diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 7b5fedb3..660733ed 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -93,6 +93,9 @@ CreateSGWidget.xaml + + CreateWARCWidget.xaml + CreateXP3Widget.xaml @@ -184,6 +187,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/ArcFormats/ArcWILL.cs b/ArcFormats/ArcWILL.cs index edaf69f2..8af29b62 100644 --- a/ArcFormats/ArcWILL.cs +++ b/ArcFormats/ArcWILL.cs @@ -23,8 +23,12 @@ // IN THE SOFTWARE. // +using System; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.IO; +using GameRes.Formats.Properties; +using GameRes.Formats.Strings; namespace GameRes.Formats.Will { @@ -35,14 +39,24 @@ namespace GameRes.Formats.Will public uint DirOffset; } + public class ArcOptions : ResourceOptions + { + public int NameLength { get; set; } + } + [Export(typeof(ArchiveFormat))] public class ArcOpener : ArchiveFormat { - public override string Tag { get { return "ARC"; } } + public override string Tag { get { return "WARC"; } } public override string Description { get { return "Will Co. game engine resource archive"; } } public override uint Signature { get { return 0; } } public override bool IsHierarchic { get { return false; } } - public override bool CanCreate { get { return false; } } + public override bool CanCreate { get { return true; } } + + ArcOpener () + { + Extensions = new string[] { "arc" }; + } public override ArcFile TryOpen (ArcView file) { @@ -52,16 +66,14 @@ namespace GameRes.Formats.Will uint dir_offset = 4; var ext_list = new List (ext_count); - int file_count = 0; for (int i = 0; i < ext_count; ++i) { string ext = file.View.ReadString (dir_offset, 4).ToLowerInvariant(); int count = file.View.ReadInt32 (dir_offset+4); uint offset = file.View.ReadUInt32 (dir_offset+8); - if (count <= 0 || count > 0xffff || offset <= dir_offset) + if (count <= 0 || count > 0xffff || offset <= dir_offset || offset > file.MaxOffset) return null; ext_list.Add (new ExtRecord { Extension = ext, FileCount = count, DirOffset = offset }); - file_count += count; dir_offset += 12; } var dir = ReadFileList (file, ext_list, 9); @@ -77,10 +89,14 @@ namespace GameRes.Formats.Will var dir = new List(); foreach (var ext in ext_list) { + dir.Capacity = dir.Count + ext.FileCount; uint dir_offset = ext.DirOffset; for (int i = 0; i < ext.FileCount; ++i) { - string name = file.View.ReadString (dir_offset, name_size).ToLowerInvariant()+'.'+ext.Extension; + string name = file.View.ReadString (dir_offset, name_size); + if (string.IsNullOrEmpty (name)) + return null; + name = name.ToLowerInvariant()+'.'+ext.Extension; var entry = FormatCatalog.Instance.CreateEntry (name); entry.Size = file.View.ReadUInt32 (dir_offset+name_size); entry.Offset = file.View.ReadUInt32 (dir_offset+name_size+4); @@ -92,5 +108,119 @@ namespace GameRes.Formats.Will } return dir; } + + public override ResourceOptions GetDefaultOptions () + { + return new ArcOptions { NameLength = Settings.Default.WARCNameLength }; + } + + public override object GetCreationWidget () + { + return new GUI.CreateWARCWidget(); + } + + internal class ArcEntry : Entry + { + public byte[] RawName; + } + + internal class ArcDirectory + { + public byte[] Extension; + public uint DirOffset; + public List Files; + } + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + var arc_options = GetOptions (options); + var encoding = Encodings.cp932.WithFatalFallback(); + + int file_count = 0; + var file_table = new SortedDictionary(); + foreach (var entry in list) + { + string ext = Path.GetExtension (entry.Name).TrimStart ('.').ToUpperInvariant(); + if (string.IsNullOrEmpty (ext)) + throw new InvalidFileName (entry.Name, arcStrings.MsgNoExtension); + if (ext.Length > 3) + throw new InvalidFileName (entry.Name, arcStrings.MsgExtensionTooLong); + string name = Path.GetFileNameWithoutExtension (entry.Name).ToUpperInvariant(); + byte[] raw_name = encoding.GetBytes (name); + if (raw_name.Length > arc_options.NameLength) + throw new InvalidFileName (entry.Name, arcStrings.MsgFileNameTooLong); + + ArcDirectory dir; + if (!file_table.TryGetValue (ext, out dir)) + { + byte[] raw_ext = encoding.GetBytes (ext); + if (raw_ext.Length > 3) + throw new InvalidFileName (entry.Name, arcStrings.MsgExtensionTooLong); + dir = new ArcDirectory { Extension = raw_ext, Files = new List() }; + file_table[ext] = dir; + } + dir.Files.Add (new ArcEntry { Name = entry.Name, RawName = raw_name }); + ++file_count; + } + if (null != callback) + callback (file_count+1, null, null); + + int callback_count = 0; + long dir_offset = 4 + file_table.Count * 12; + long data_offset = dir_offset + (arc_options.NameLength + 9) * file_count; + output.Position = data_offset; + foreach (var ext in file_table.Keys) + { + var dir = file_table[ext]; + dir.DirOffset = (uint)dir_offset; + dir_offset += (arc_options.NameLength + 9) * dir.Files.Count; + foreach (var entry in dir.Files) + { + if (null != callback) + callback (callback_count++, entry, arcStrings.MsgAddingFile); + entry.Offset = data_offset; + using (var input = File.OpenRead (entry.Name)) + { + var size = input.Length; + if (size > uint.MaxValue || data_offset + size > uint.MaxValue) + throw new FileSizeException(); + data_offset += size; + entry.Size = (uint)size; + input.CopyTo (output); + } + } + } + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + + output.Position = 0; + using (var header = new BinaryWriter (output, encoding, true)) + { + byte[] buffer = new byte[arc_options.NameLength+1]; + header.Write (file_table.Count); + foreach (var ext in file_table) + { + Array.Copy (ext.Value.Extension, buffer, ext.Value.Extension.Length); + for (int i = ext.Value.Extension.Length; i < 4; ++i) + buffer[i] = 0; + header.Write (buffer, 0, 4); + header.Write (ext.Value.Files.Count); + header.Write (ext.Value.DirOffset); + } + foreach (var ext in file_table) + { + foreach (var entry in ext.Value.Files) + { + Array.Copy (entry.RawName, buffer, entry.RawName.Length); + for (int i = entry.RawName.Length; i < buffer.Length; ++i) + buffer[i] = 0; + header.Write (buffer); + header.Write (entry.Size); + header.Write ((uint)entry.Offset); + } + } + } + } } } diff --git a/ArcFormats/CreateWARCWidget.xaml b/ArcFormats/CreateWARCWidget.xaml new file mode 100644 index 00000000..2360976b --- /dev/null +++ b/ArcFormats/CreateWARCWidget.xaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/ArcFormats/CreateWARCWidget.xaml.cs b/ArcFormats/CreateWARCWidget.xaml.cs new file mode 100644 index 00000000..6fd5e086 --- /dev/null +++ b/ArcFormats/CreateWARCWidget.xaml.cs @@ -0,0 +1,15 @@ +using System.Windows.Controls; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for CreateWARCWidget.xaml + /// + public partial class CreateWARCWidget : Grid + { + public CreateWARCWidget () + { + InitializeComponent (); + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index 3287c38f..1a18288e 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.0.2.20")] -[assembly: AssemblyFileVersion ("1.0.2.20")] +[assembly: AssemblyVersion ("1.0.2.21")] +[assembly: AssemblyFileVersion ("1.0.2.21")] diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 8b41edd0..523f8e8e 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -237,5 +237,17 @@ namespace GameRes.Formats.Properties { this["NPAKey2"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("8")] + public int WARCNameLength { + get { + return ((int)(this["WARCNameLength"])); + } + set { + this["WARCNameLength"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index da1a068d..1085099c 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -56,5 +56,8 @@ 555831124 + + 8 + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index 23e6886b..6f0e8b49 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -279,6 +279,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to File name extension too long.. + /// + public static string MsgExtensionTooLong { + get { + return ResourceManager.GetString("MsgExtensionTooLong", resourceCulture); + } + } + /// /// Looks up a localized string similar to File name is too long. /// @@ -315,6 +324,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to File name without extension.. + /// + public static string MsgNoExtension { + get { + return ResourceManager.GetString("MsgNoExtension", resourceCulture); + } + } + /// /// Looks up a localized string similar to Number of files exceedes archive limit.. /// @@ -487,6 +505,16 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Maximum file name length + ///(not including extension). + /// + public static string WARCLabelLength { + get { + return ResourceManager.GetString("WARCLabelLength", resourceCulture); + } + } + /// /// Looks up a localized string similar to Liar-soft game resource archive. /// diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index ce0df232..a0b602ea 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -192,6 +192,9 @@ predefined encryption scheme. Encryption method not implemented + + File name extension too long. + File name is too long @@ -204,6 +207,9 @@ predefined encryption scheme. Invalid archive version specified. + + File name without extension. + Number of files exceedes archive limit. @@ -262,6 +268,10 @@ predefined encryption scheme. Hex number + + Maximum file name length +(not including extension) + Liar-soft game resource archive diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx index 84862a7f..aeb8bbb9 100644 --- a/ArcFormats/Strings/arcStrings.ru-RU.resx +++ b/ArcFormats/Strings/arcStrings.ru-RU.resx @@ -174,6 +174,9 @@ Метод шифрования не реализован + + Слишком длинное расширение имени файла. + Слишком длинное имя файла @@ -186,6 +189,9 @@ Указана некорректная версия архива. + + Имя файла без расширения. + Количество файлов превышает ограничения архива. @@ -226,6 +232,10 @@ Шестнадцатеричное число + + Максимальная длина имени файла +(не считая расширения) + Сжать содержимое diff --git a/ArcFormats/app.config b/ArcFormats/app.config index cdb202d3..476a6b4d 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -58,6 +58,9 @@ 555831124 + + 8 +