diff --git a/ArcFormats/ArcAZSys.cs b/ArcFormats/AZSys/ArcAZSys.cs similarity index 100% rename from ArcFormats/ArcAZSys.cs rename to ArcFormats/AZSys/ArcAZSys.cs diff --git a/ArcFormats/ImageCPB.cs b/ArcFormats/AZSys/ImageCPB.cs similarity index 100% rename from ArcFormats/ImageCPB.cs rename to ArcFormats/AZSys/ImageCPB.cs diff --git a/ArcFormats/ArcADPACK.cs b/ArcFormats/ActiveSoft/ArcADPACK.cs similarity index 100% rename from ArcFormats/ArcADPACK.cs rename to ArcFormats/ActiveSoft/ArcADPACK.cs diff --git a/ArcFormats/ImageEDT.cs b/ArcFormats/ActiveSoft/ImageEDT.cs similarity index 100% rename from ArcFormats/ImageEDT.cs rename to ArcFormats/ActiveSoft/ImageEDT.cs diff --git a/ArcFormats/ArcAdvSysT.cs b/ArcFormats/AdvSys/ArcAdvSysT.cs similarity index 100% rename from ArcFormats/ArcAdvSysT.cs rename to ArcFormats/AdvSys/ArcAdvSysT.cs diff --git a/ArcFormats/ImageGR2.cs b/ArcFormats/AdvSys/ImageGR2.cs similarity index 100% rename from ArcFormats/ImageGR2.cs rename to ArcFormats/AdvSys/ImageGR2.cs diff --git a/ArcFormats/ArcALD.cs b/ArcFormats/AliceSoft/ArcALD.cs similarity index 100% rename from ArcFormats/ArcALD.cs rename to ArcFormats/AliceSoft/ArcALD.cs diff --git a/ArcFormats/ImageQNT.cs b/ArcFormats/AliceSoft/ImageQNT.cs similarity index 100% rename from ArcFormats/ImageQNT.cs rename to ArcFormats/AliceSoft/ImageQNT.cs diff --git a/ArcFormats/ArcAMI.cs b/ArcFormats/Amaterasu/ArcAMI.cs similarity index 82% rename from ArcFormats/ArcAMI.cs rename to ArcFormats/Amaterasu/ArcAMI.cs index 43e56b8f..7def95b2 100644 --- a/ArcFormats/ArcAMI.cs +++ b/ArcFormats/Amaterasu/ArcAMI.cs @@ -1,515 +1,436 @@ -//! \file ArcAMI.cs -//! \date Thu Jul 03 09:40:40 2014 -//! \brief Muv-Luv Amaterasu Translation archive. -// -// 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.IO; -using System.Text; -using System.Linq; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Globalization; -using System.Windows; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using GameRes.Compression; -using GameRes.Formats.Strings; -using GameRes.Formats.Properties; - -namespace GameRes.Formats -{ - internal class AmiEntry : PackedEntry - { - public uint Id; - - private Lazy m_ext; - private Lazy m_name; - private Lazy m_type; - public override string Name - { - get { return m_name.Value; } - set { m_name = new Lazy (() => value); } - } - public override string Type - { - get { return m_type.Value; } - set { m_type = new Lazy (() => value); } - } - - public AmiEntry (uint id, Func ext_factory) - { - Id = id; - m_ext = new Lazy (ext_factory); - m_name = new Lazy (GetName); - m_type = new Lazy (GetEntryType); - } - - private string GetName () - { - return string.Format ("{0:x8}.{1}", Id, m_ext.Value); - } - - private string GetEntryType () - { - var ext = m_ext.Value; - if ("grp" == ext) - return "image"; - if ("scr" == ext) - return "script"; - return ""; - } - } - - internal class AmiOptions : ResourceOptions - { - public bool UseBaseArchive; - public string BaseArchive; - } - - [Export(typeof(ArchiveFormat))] - public class AmiOpener : ArchiveFormat - { - public override string Tag { get { return "AMI"; } } - public override string Description { get { return Strings.arcStrings.AMIDescription; } } - public override uint Signature { get { return 0x00494d41; } } - public override bool IsHierarchic { get { return false; } } - public override bool CanCreate { get { return true; } } - - public AmiOpener () - { - Extensions = new string[] { "ami", "amr" }; - } - - public override ArcFile TryOpen (ArcView file) - { - int count = file.View.ReadInt32 (4); - if (count <= 0) - return null; - uint base_offset = file.View.ReadUInt32 (8); - long max_offset = file.MaxOffset; - if (base_offset >= max_offset) - return null; - - uint cur_offset = 16; - var dir = new List (count); - for (int i = 0; i < count; ++i) - { - if (cur_offset+16 > base_offset) - return null; - uint id = file.View.ReadUInt32 (cur_offset); - uint offset = file.View.ReadUInt32 (cur_offset+4); - uint size = file.View.ReadUInt32 (cur_offset+8); - uint packed_size = file.View.ReadUInt32 (cur_offset+12); - - var entry = new AmiEntry (id, () => { - uint signature = file.View.ReadUInt32 (offset); - if (0x00524353 == signature) - return "scr"; - else if (0 != packed_size || 0x00505247 == signature) - return "grp"; - else - return "dat"; - }); - - entry.Offset = offset; - entry.UnpackedSize = size; - entry.IsPacked = 0 != packed_size; - entry.Size = entry.IsPacked ? packed_size : size; - if (!entry.CheckPlacement (max_offset)) - return null; - dir.Add (entry); - cur_offset += 16; - } - return new ArcFile (file, this, dir); - } - - public override Stream OpenEntry (ArcFile arc, Entry entry) - { - var input = arc.File.CreateStream (entry.Offset, entry.Size); - var packed_entry = entry as AmiEntry; - if (null == packed_entry || !packed_entry.IsPacked) - return input; - else - return new ZLibStream (input, CompressionMode.Decompress); - } - - public override void Create (Stream output, IEnumerable list, ResourceOptions options, - EntryCallback callback) - { - ArcFile base_archive = null; - var ami_options = GetOptions (options); - if (null != ami_options && ami_options.UseBaseArchive && !string.IsNullOrEmpty (ami_options.BaseArchive)) - { - var base_file = new ArcView (ami_options.BaseArchive); - try - { - if (base_file.View.ReadUInt32(0) == Signature) - base_archive = TryOpen (base_file); - if (null == base_archive) - throw new InvalidFormatException (string.Format ("{0}: base archive could not be read", - Path.GetFileName (ami_options.BaseArchive))); - base_file = null; - } - finally - { - if (null != base_file) - base_file.Dispose(); - } - } - try - { - var file_table = new SortedDictionary(); - if (null != base_archive) - { - foreach (var entry in base_archive.Dir.Cast()) - file_table[entry.Id] = entry; - } - int update_count = UpdateFileTable (file_table, list); - if (0 == update_count) - throw new InvalidFormatException (arcStrings.AMINoFiles); - - uint file_count = (uint)file_table.Count; - if (null != callback) - callback ((int)file_count+1, null, null); - - int callback_count = 0; - long start_offset = output.Position; - uint data_offset = file_count * 16 + 16; - output.Seek (data_offset, SeekOrigin.Current); - foreach (var entry in file_table) - { - if (null != callback) - callback (callback_count++, entry.Value, arcStrings.MsgAddingFile); - long current_offset = output.Position; - if (current_offset > uint.MaxValue) - throw new FileSizeException(); - if (entry.Value is AmiEntry) - CopyAmiEntry (base_archive, entry.Value, output); - else - entry.Value.Size = WriteAmiEntry (entry.Value, output); - entry.Value.Offset = (uint)current_offset; - } - if (null != callback) - callback (callback_count++, null, arcStrings.MsgWritingIndex); - output.Position = start_offset; - using (var header = new BinaryWriter (output, Encoding.ASCII, true)) - { - header.Write (Signature); - header.Write (file_count); - header.Write (data_offset); - header.Write ((uint)0); - foreach (var entry in file_table) - { - header.Write (entry.Key); - header.Write ((uint)entry.Value.Offset); - header.Write ((uint)entry.Value.UnpackedSize); - header.Write ((uint)entry.Value.Size); - } - } - } - finally - { - if (null != base_archive) - base_archive.Dispose(); - } - } - - int UpdateFileTable (IDictionary table, IEnumerable list) - { - int update_count = 0; - foreach (var entry in list) - { - if (entry.Type != "image" && !entry.Name.EndsWith (".scr", StringComparison.InvariantCultureIgnoreCase)) - continue; - uint id; - if (!uint.TryParse (Path.GetFileNameWithoutExtension (entry.Name), NumberStyles.HexNumber, - CultureInfo.InvariantCulture, out id)) - continue; - PackedEntry existing; - if (table.TryGetValue (id, out existing) && !(existing is AmiEntry)) - { - var file_new = new FileInfo (entry.Name); - if (!file_new.Exists) - continue; - var file_old = new FileInfo (existing.Name); - if (file_new.LastWriteTime <= file_old.LastWriteTime) - continue; - } - table[id] = new PackedEntry - { - Name = entry.Name, - Type = entry.Type - }; - ++update_count; - } - return update_count; - } - - void CopyAmiEntry (ArcFile base_archive, Entry entry, Stream output) - { - using (var input = base_archive.File.CreateStream (entry.Offset, entry.Size)) - input.CopyTo (output); - } - - uint WriteAmiEntry (PackedEntry entry, Stream output) - { - uint packed_size = 0; - using (var input = File.OpenRead (entry.Name)) - { - long file_size = input.Length; - if (file_size > uint.MaxValue) - throw new FileSizeException(); - entry.UnpackedSize = (uint)file_size; - if ("image" == entry.Type) - { - packed_size = WriteImageEntry (entry, input, output); - } - else - { - input.CopyTo (output); - } - } - return packed_size; - } - - static Lazy s_grp_format = new Lazy (() => - FormatCatalog.Instance.ImageFormats.OfType().FirstOrDefault()); - - uint WriteImageEntry (PackedEntry entry, Stream input, Stream output) - { - var grp = s_grp_format.Value; - if (null == grp) // probably never happens - throw new FileFormatException ("GRP image encoder not available"); - bool is_grp = grp.Signature == FormatCatalog.ReadSignature (input); - input.Position = 0; - var start = output.Position; - using (var zstream = new ZLibStream (output, CompressionMode.Compress, CompressionLevel.Level9, true)) - { - if (is_grp) - { - input.CopyTo (zstream); - } - else - { - var image = ImageFormat.Read (input); - if (null == image) - throw new InvalidFormatException (string.Format (arcStrings.MsgInvalidImageFormat, entry.Name)); - grp.Write (zstream, image); - entry.UnpackedSize = (uint)zstream.TotalIn; - } - } - return (uint)(output.Position - start); - } - - public override ResourceOptions GetDefaultOptions () - { - return new AmiOptions { - UseBaseArchive = Settings.Default.AMIUseBaseArchive, - BaseArchive = Settings.Default.AMIBaseArchive, - }; - } - - public override object GetCreationWidget () - { - return new GUI.CreateAMIWidget(); - } - } - - [Export(typeof(ImageFormat))] - public class GrpFormat : ImageFormat - { - public override string Tag { get { return "GRP"; } } - public override string Description { get { return Strings.arcStrings.GRPDescription; } } - public override uint Signature { get { return 0x00505247; } } - - public override ImageMetaData ReadMetaData (Stream stream) - { - using (var file = new BinaryReader (stream, Encoding.ASCII, true)) - { - if (file.ReadUInt32() != Signature) - return null; - var meta = new ImageMetaData(); - meta.OffsetX = file.ReadInt16(); - meta.OffsetY = file.ReadInt16(); - meta.Width = file.ReadUInt16(); - meta.Height = file.ReadUInt16(); - meta.BPP = 32; - stream.Position = 0; - return meta; - } - } - - public override ImageData Read (Stream file, ImageMetaData info) - { - int width = (int)info.Width; - int height = (int)info.Height; - int stride = width*4; - byte[] pixels = new byte[stride*height]; - file.Position = 12; - for (int row = height-1; row >= 0; --row) - { - if (stride != file.Read (pixels, row*stride, stride)) - throw new InvalidFormatException(); - } - var bitmap = BitmapSource.Create (width, height, 96, 96, - PixelFormats.Bgra32, null, pixels, stride); - bitmap.Freeze(); - - return new ImageData (bitmap, info); - } - - public override void Write (Stream stream, ImageData image) - { - using (var file = new BinaryWriter (stream, Encoding.ASCII, true)) - { - file.Write (Signature); - file.Write ((short)image.OffsetX); - file.Write ((short)image.OffsetY); - file.Write ((ushort)image.Width); - file.Write ((ushort)image.Height); - - var bitmap = image.Bitmap; - if (bitmap.Format != PixelFormats.Bgra32) - { - var converted_bitmap = new FormatConvertedBitmap(); - converted_bitmap.BeginInit(); - converted_bitmap.Source = image.Bitmap; - converted_bitmap.DestinationFormat = PixelFormats.Bgra32; - converted_bitmap.EndInit(); - bitmap = converted_bitmap; - } - int stride = (int)image.Width * 4; - byte[] row_data = new byte[stride]; - Int32Rect rect = new Int32Rect (0, (int)image.Height, (int)image.Width, 1); - for (uint row = 0; row < image.Height; ++row) - { - --rect.Y; - bitmap.CopyPixels (rect, row_data, stride, 0); - file.Write (row_data); - } - } - } - } - - public class AmiScriptData : ScriptData - { - public uint Id; - public uint Type; - } - - [Export(typeof(ScriptFormat))] - public class ScrFormat : ScriptFormat - { - public override string Tag { get { return "SCR"; } } - public override string Description { get { return Strings.arcStrings.SCRDescription; } } - public override uint Signature { get { return 0x00524353; } } - - public override ScriptData Read (string name, Stream stream) - { - if (Signature != FormatCatalog.ReadSignature (stream)) - return null; - uint script_id = Convert.ToUInt32 (name, 16); - uint max_offset = (uint)Math.Min (stream.Length, 0xffffffff); - - using (var file = new BinaryReader (stream, Encodings.cp932, true)) - { - uint script_type = file.ReadUInt32(); - var script = new AmiScriptData { - Id = script_id, - Type = script_type - }; - uint count = file.ReadUInt32(); - for (uint i = 0; i < count; ++i) - { - uint offset = file.ReadUInt32(); - if (offset >= max_offset) - throw new InvalidFormatException ("Invalid offset in script data file"); - int size = file.ReadInt32(); - uint id = file.ReadUInt32(); - var header_pos = file.BaseStream.Position; - file.BaseStream.Position = offset; - byte[] line = file.ReadBytes (size); - if (line.Length != size) - throw new InvalidFormatException ("Premature end of file"); - string text = Encodings.cp932.GetString (line); - - script.TextLines.Add (new ScriptLine { Id = id, Text = text }); - file.BaseStream.Position = header_pos; - } - return script; - } - } - - public string GetName (ScriptData script_data) - { - var script = script_data as AmiScriptData; - if (null != script) - return script.Id.ToString ("x8"); - else - return null; - } - - struct IndexEntry - { - public uint offset, size, id; - } - - public override void Write (Stream stream, ScriptData script_data) - { - var script = script_data as AmiScriptData; - if (null == script) - throw new ArgumentException ("Illegal ScriptData", "script_data"); - using (var file = new BinaryWriter (stream, Encodings.cp932, true)) - { - file.Write (Signature); - file.Write (script.Type); - uint count = (uint)script.TextLines.Count; - file.Write (count); - var index_pos = file.BaseStream.Position; - file.Seek ((int)count*12, SeekOrigin.Current); - var index = new IndexEntry[count]; - int i = 0; - foreach (var line in script.TextLines) - { - var text = Encodings.cp932.GetBytes (line.Text); - index[i].offset = (uint)file.BaseStream.Position; - index[i].size = (uint)text.Length; - index[i].id = line.Id; - file.Write (text); - file.Write ((byte)0); - ++i; - } - var end_pos = file.BaseStream.Position; - file.BaseStream.Position = index_pos; - foreach (var entry in index) - { - file.Write (entry.offset); - file.Write (entry.size); - file.Write (entry.id); - } - file.BaseStream.Position = end_pos; - } - } - } -} +//! \file ArcAMI.cs +//! \date Thu Jul 03 09:40:40 2014 +//! \brief Muv-Luv Amaterasu Translation archive. +// +// 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.IO; +using System.Text; +using System.Linq; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Globalization; +using GameRes.Compression; +using GameRes.Formats.Strings; +using GameRes.Formats.Properties; + +namespace GameRes.Formats.Amaterasu +{ + internal class AmiEntry : PackedEntry + { + public uint Id; + + private Lazy m_ext; + private Lazy m_name; + private Lazy m_type; + public override string Name + { + get { return m_name.Value; } + set { m_name = new Lazy (() => value); } + } + public override string Type + { + get { return m_type.Value; } + set { m_type = new Lazy (() => value); } + } + + public AmiEntry (uint id, Func ext_factory) + { + Id = id; + m_ext = new Lazy (ext_factory); + m_name = new Lazy (GetName); + m_type = new Lazy (GetEntryType); + } + + private string GetName () + { + return string.Format ("{0:x8}.{1}", Id, m_ext.Value); + } + + private string GetEntryType () + { + var ext = m_ext.Value; + if ("grp" == ext) + return "image"; + if ("scr" == ext) + return "script"; + return ""; + } + } + + internal class AmiOptions : ResourceOptions + { + public bool UseBaseArchive; + public string BaseArchive; + } + + [Export(typeof(ArchiveFormat))] + public class AmiOpener : ArchiveFormat + { + public override string Tag { get { return "AMI"; } } + public override string Description { get { return Strings.arcStrings.AMIDescription; } } + public override uint Signature { get { return 0x00494d41; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return true; } } + + public AmiOpener () + { + Extensions = new string[] { "ami", "amr" }; + } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (count <= 0) + return null; + uint base_offset = file.View.ReadUInt32 (8); + long max_offset = file.MaxOffset; + if (base_offset >= max_offset) + return null; + + uint cur_offset = 16; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + if (cur_offset+16 > base_offset) + return null; + uint id = file.View.ReadUInt32 (cur_offset); + uint offset = file.View.ReadUInt32 (cur_offset+4); + uint size = file.View.ReadUInt32 (cur_offset+8); + uint packed_size = file.View.ReadUInt32 (cur_offset+12); + + var entry = new AmiEntry (id, () => { + uint signature = file.View.ReadUInt32 (offset); + if (0x00524353 == signature) + return "scr"; + else if (0 != packed_size || 0x00505247 == signature) + return "grp"; + else + return "dat"; + }); + + entry.Offset = offset; + entry.UnpackedSize = size; + entry.IsPacked = 0 != packed_size; + entry.Size = entry.IsPacked ? packed_size : size; + if (!entry.CheckPlacement (max_offset)) + return null; + dir.Add (entry); + cur_offset += 16; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var packed_entry = entry as AmiEntry; + if (null == packed_entry || !packed_entry.IsPacked) + return input; + else + return new ZLibStream (input, CompressionMode.Decompress); + } + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + ArcFile base_archive = null; + var ami_options = GetOptions (options); + if (null != ami_options && ami_options.UseBaseArchive && !string.IsNullOrEmpty (ami_options.BaseArchive)) + { + var base_file = new ArcView (ami_options.BaseArchive); + try + { + if (base_file.View.ReadUInt32(0) == Signature) + base_archive = TryOpen (base_file); + if (null == base_archive) + throw new InvalidFormatException (string.Format ("{0}: base archive could not be read", + Path.GetFileName (ami_options.BaseArchive))); + base_file = null; + } + finally + { + if (null != base_file) + base_file.Dispose(); + } + } + try + { + var file_table = new SortedDictionary(); + if (null != base_archive) + { + foreach (var entry in base_archive.Dir.Cast()) + file_table[entry.Id] = entry; + } + int update_count = UpdateFileTable (file_table, list); + if (0 == update_count) + throw new InvalidFormatException (arcStrings.AMINoFiles); + + uint file_count = (uint)file_table.Count; + if (null != callback) + callback ((int)file_count+1, null, null); + + int callback_count = 0; + long start_offset = output.Position; + uint data_offset = file_count * 16 + 16; + output.Seek (data_offset, SeekOrigin.Current); + foreach (var entry in file_table) + { + if (null != callback) + callback (callback_count++, entry.Value, arcStrings.MsgAddingFile); + long current_offset = output.Position; + if (current_offset > uint.MaxValue) + throw new FileSizeException(); + if (entry.Value is AmiEntry) + CopyAmiEntry (base_archive, entry.Value, output); + else + entry.Value.Size = WriteAmiEntry (entry.Value, output); + entry.Value.Offset = (uint)current_offset; + } + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + output.Position = start_offset; + using (var header = new BinaryWriter (output, Encoding.ASCII, true)) + { + header.Write (Signature); + header.Write (file_count); + header.Write (data_offset); + header.Write ((uint)0); + foreach (var entry in file_table) + { + header.Write (entry.Key); + header.Write ((uint)entry.Value.Offset); + header.Write ((uint)entry.Value.UnpackedSize); + header.Write ((uint)entry.Value.Size); + } + } + } + finally + { + if (null != base_archive) + base_archive.Dispose(); + } + } + + int UpdateFileTable (IDictionary table, IEnumerable list) + { + int update_count = 0; + foreach (var entry in list) + { + if (entry.Type != "image" && !entry.Name.EndsWith (".scr", StringComparison.InvariantCultureIgnoreCase)) + continue; + uint id; + if (!uint.TryParse (Path.GetFileNameWithoutExtension (entry.Name), NumberStyles.HexNumber, + CultureInfo.InvariantCulture, out id)) + continue; + PackedEntry existing; + if (table.TryGetValue (id, out existing) && !(existing is AmiEntry)) + { + var file_new = new FileInfo (entry.Name); + if (!file_new.Exists) + continue; + var file_old = new FileInfo (existing.Name); + if (file_new.LastWriteTime <= file_old.LastWriteTime) + continue; + } + table[id] = new PackedEntry + { + Name = entry.Name, + Type = entry.Type + }; + ++update_count; + } + return update_count; + } + + void CopyAmiEntry (ArcFile base_archive, Entry entry, Stream output) + { + using (var input = base_archive.File.CreateStream (entry.Offset, entry.Size)) + input.CopyTo (output); + } + + uint WriteAmiEntry (PackedEntry entry, Stream output) + { + uint packed_size = 0; + using (var input = File.OpenRead (entry.Name)) + { + long file_size = input.Length; + if (file_size > uint.MaxValue) + throw new FileSizeException(); + entry.UnpackedSize = (uint)file_size; + if ("image" == entry.Type) + { + packed_size = WriteImageEntry (entry, input, output); + } + else + { + input.CopyTo (output); + } + } + return packed_size; + } + + static Lazy s_grp_format = new Lazy (() => + FormatCatalog.Instance.ImageFormats.OfType().FirstOrDefault()); + + uint WriteImageEntry (PackedEntry entry, Stream input, Stream output) + { + var grp = s_grp_format.Value; + if (null == grp) // probably never happens + throw new FileFormatException ("GRP image encoder not available"); + bool is_grp = grp.Signature == FormatCatalog.ReadSignature (input); + input.Position = 0; + var start = output.Position; + using (var zstream = new ZLibStream (output, CompressionMode.Compress, CompressionLevel.Level9, true)) + { + if (is_grp) + { + input.CopyTo (zstream); + } + else + { + var image = ImageFormat.Read (input); + if (null == image) + throw new InvalidFormatException (string.Format (arcStrings.MsgInvalidImageFormat, entry.Name)); + grp.Write (zstream, image); + entry.UnpackedSize = (uint)zstream.TotalIn; + } + } + return (uint)(output.Position - start); + } + + public override ResourceOptions GetDefaultOptions () + { + return new AmiOptions { + UseBaseArchive = Settings.Default.AMIUseBaseArchive, + BaseArchive = Settings.Default.AMIBaseArchive, + }; + } + + public override object GetCreationWidget () + { + return new GUI.CreateAMIWidget(); + } + } + + public class AmiScriptData : ScriptData + { + public uint Id; + public uint Type; + } + + [Export(typeof(ScriptFormat))] + public class ScrFormat : ScriptFormat + { + public override string Tag { get { return "SCR"; } } + public override string Description { get { return Strings.arcStrings.SCRDescription; } } + public override uint Signature { get { return 0x00524353; } } + + public override ScriptData Read (string name, Stream stream) + { + if (Signature != FormatCatalog.ReadSignature (stream)) + return null; + uint script_id = Convert.ToUInt32 (name, 16); + uint max_offset = (uint)Math.Min (stream.Length, 0xffffffff); + + using (var file = new BinaryReader (stream, Encodings.cp932, true)) + { + uint script_type = file.ReadUInt32(); + var script = new AmiScriptData { + Id = script_id, + Type = script_type + }; + uint count = file.ReadUInt32(); + for (uint i = 0; i < count; ++i) + { + uint offset = file.ReadUInt32(); + if (offset >= max_offset) + throw new InvalidFormatException ("Invalid offset in script data file"); + int size = file.ReadInt32(); + uint id = file.ReadUInt32(); + var header_pos = file.BaseStream.Position; + file.BaseStream.Position = offset; + byte[] line = file.ReadBytes (size); + if (line.Length != size) + throw new InvalidFormatException ("Premature end of file"); + string text = Encodings.cp932.GetString (line); + + script.TextLines.Add (new ScriptLine { Id = id, Text = text }); + file.BaseStream.Position = header_pos; + } + return script; + } + } + + public string GetName (ScriptData script_data) + { + var script = script_data as AmiScriptData; + if (null != script) + return script.Id.ToString ("x8"); + else + return null; + } + + struct IndexEntry + { + public uint offset, size, id; + } + + public override void Write (Stream stream, ScriptData script_data) + { + var script = script_data as AmiScriptData; + if (null == script) + throw new ArgumentException ("Illegal ScriptData", "script_data"); + using (var file = new BinaryWriter (stream, Encodings.cp932, true)) + { + file.Write (Signature); + file.Write (script.Type); + uint count = (uint)script.TextLines.Count; + file.Write (count); + var index_pos = file.BaseStream.Position; + file.Seek ((int)count*12, SeekOrigin.Current); + var index = new IndexEntry[count]; + int i = 0; + foreach (var line in script.TextLines) + { + var text = Encodings.cp932.GetBytes (line.Text); + index[i].offset = (uint)file.BaseStream.Position; + index[i].size = (uint)text.Length; + index[i].id = line.Id; + file.Write (text); + file.Write ((byte)0); + ++i; + } + var end_pos = file.BaseStream.Position; + file.BaseStream.Position = index_pos; + foreach (var entry in index) + { + file.Write (entry.offset); + file.Write (entry.size); + file.Write (entry.id); + } + file.BaseStream.Position = end_pos; + } + } + } +} diff --git a/ArcFormats/CreateAMIWidget.xaml b/ArcFormats/Amaterasu/CreateAMIWidget.xaml similarity index 100% rename from ArcFormats/CreateAMIWidget.xaml rename to ArcFormats/Amaterasu/CreateAMIWidget.xaml diff --git a/ArcFormats/CreateAMIWidget.xaml.cs b/ArcFormats/Amaterasu/CreateAMIWidget.xaml.cs similarity index 100% rename from ArcFormats/CreateAMIWidget.xaml.cs rename to ArcFormats/Amaterasu/CreateAMIWidget.xaml.cs diff --git a/ArcFormats/Amaterasu/ImageGRP.cs b/ArcFormats/Amaterasu/ImageGRP.cs new file mode 100644 index 00000000..48e21e6b --- /dev/null +++ b/ArcFormats/Amaterasu/ImageGRP.cs @@ -0,0 +1,100 @@ +//! \file ImageGRP.cs +//! \date Wed Aug 19 20:58:51 2015 +//! \brief Muv-Luv Amaterasu Translation RGB bitmap. +// +// 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 +// 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.IO; +using System.Text; +using System.ComponentModel.Composition; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Amaterasu +{ + [Export(typeof(ImageFormat))] + public class GrpFormat : ImageFormat + { + public override string Tag { get { return "GRP"; } } + public override string Description { get { return Strings.arcStrings.GRPDescription; } } + public override uint Signature { get { return 0x00505247; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + using (var file = new BinaryReader (stream, Encoding.ASCII, true)) + { + if (file.ReadUInt32() != Signature) + return null; + var meta = new ImageMetaData(); + meta.OffsetX = file.ReadInt16(); + meta.OffsetY = file.ReadInt16(); + meta.Width = file.ReadUInt16(); + meta.Height = file.ReadUInt16(); + meta.BPP = 32; + return meta; + } + } + + public override ImageData Read (Stream file, ImageMetaData info) + { + int width = (int)info.Width; + int height = (int)info.Height; + int stride = width*4; + byte[] pixels = new byte[stride*height]; + file.Position = 12; + for (int row = height-1; row >= 0; --row) + { + if (stride != file.Read (pixels, row*stride, stride)) + throw new InvalidFormatException(); + } + return ImageData.Create (info, PixelFormats.Bgra32, null, pixels); + } + + public override void Write (Stream stream, ImageData image) + { + using (var file = new BinaryWriter (stream, Encoding.ASCII, true)) + { + file.Write (Signature); + file.Write ((short)image.OffsetX); + file.Write ((short)image.OffsetY); + file.Write ((ushort)image.Width); + file.Write ((ushort)image.Height); + + var bitmap = image.Bitmap; + if (bitmap.Format != PixelFormats.Bgra32) + { + bitmap = new FormatConvertedBitmap (image.Bitmap, PixelFormats.Bgra32, null, 0); + } + int stride = (int)image.Width * 4; + byte[] row_data = new byte[stride]; + Int32Rect rect = new Int32Rect (0, (int)image.Height, (int)image.Width, 1); + for (uint row = 0; row < image.Height; ++row) + { + --rect.Y; + bitmap.CopyPixels (rect, row_data, stride, 0); + file.Write (row_data); + } + } + } + } +} diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index a5a121a3..bf0ddc07 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -61,199 +61,200 @@ - - + + + - + - + - - - + + + - - - - - - + + + + + + - - - + + + - - + + - - - - - - - + + + + + + + - + - - - + + + - - + + - - - - + + + + - - + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + WidgetRCT.xaml - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + - + CreateAMIWidget.xaml - + CreateINTWidget.xaml - + CreateNPAWidget.xaml - + CreateONSWidget.xaml - + CreatePDWidget.xaml - + CreateRPAWidget.xaml - + CreateSGWidget.xaml - + CreateARCWidget.xaml - + CreateXP3Widget.xaml - + CreateYPFWidget.xaml - - + + - - + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + @@ -269,38 +270,38 @@ True arcStrings.resx - + WidgetDPK.xaml - + WidgetINT.xaml - + WidgetISF.xaml - + WidgetKCAP.xaml - + Code WidgetLPK.xaml - + WidgetMBL.xaml - + WidgetNOA.xaml - + WidgetNPA.xaml - + WidgetWARC.xaml - + WidgetXP3.xaml - + WidgetYPF.xaml @@ -328,91 +329,91 @@ - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + MSBuild:Compile Designer - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + MSBuild:Compile Designer - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + MSBuild:Compile Designer - + Designer MSBuild:Compile - + Designer MSBuild:Compile @@ -424,6 +425,7 @@ + perl "$(SolutionDir)inc-revision.pl" "$(ProjectPath)" $(ConfigurationName) diff --git a/ArcFormats/ArcGPK.cs b/ArcFormats/BlackCyc/ArcGPK.cs similarity index 100% rename from ArcFormats/ArcGPK.cs rename to ArcFormats/BlackCyc/ArcGPK.cs diff --git a/ArcFormats/ArcVPK.cs b/ArcFormats/BlackCyc/ArcVPK.cs similarity index 100% rename from ArcFormats/ArcVPK.cs rename to ArcFormats/BlackCyc/ArcVPK.cs diff --git a/ArcFormats/AudioVAW.cs b/ArcFormats/BlackCyc/AudioVAW.cs similarity index 100% rename from ArcFormats/AudioVAW.cs rename to ArcFormats/BlackCyc/AudioVAW.cs diff --git a/ArcFormats/ImageDWQ.cs b/ArcFormats/BlackCyc/ImageDWQ.cs similarity index 100% rename from ArcFormats/ImageDWQ.cs rename to ArcFormats/BlackCyc/ImageDWQ.cs diff --git a/ArcFormats/ArcGSP.cs b/ArcFormats/BlackRainbow/ArcGSP.cs similarity index 100% rename from ArcFormats/ArcGSP.cs rename to ArcFormats/BlackRainbow/ArcGSP.cs diff --git a/ArcFormats/ImageBMD.cs b/ArcFormats/BlackRainbow/ImageBMD.cs similarity index 100% rename from ArcFormats/ImageBMD.cs rename to ArcFormats/BlackRainbow/ImageBMD.cs diff --git a/ArcFormats/ImageBMZ.cs b/ArcFormats/BlackRainbow/ImageBMZ.cs similarity index 100% rename from ArcFormats/ImageBMZ.cs rename to ArcFormats/BlackRainbow/ImageBMZ.cs diff --git a/ArcFormats/ArcINT.cs b/ArcFormats/CatSystem/ArcINT.cs similarity index 97% rename from ArcFormats/ArcINT.cs rename to ArcFormats/CatSystem/ArcINT.cs index dbdf83c4..8cdcf111 100644 --- a/ArcFormats/ArcINT.cs +++ b/ArcFormats/CatSystem/ArcINT.cs @@ -1,425 +1,425 @@ -//! \file ArcINT.cs -//! \date Fri Jul 11 09:32:36 2014 -//! \brief Frontwing games archive. -// -// 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.IO; -using System.Linq; -using System.Text; -using System.ComponentModel.Composition; -using System.Collections.Generic; -using System.Diagnostics; -using Simias.Encryption; -using System.Runtime.InteropServices; -using GameRes.Formats.Strings; -using GameRes.Formats.Properties; - -namespace GameRes.Formats -{ - public class FrontwingArchive : ArcFile - { - public readonly Blowfish Encryption; - - public FrontwingArchive (ArcView arc, ArchiveFormat impl, ICollection dir, Blowfish cipher) - : base (arc, impl, dir) - { - Encryption = cipher; - } - } - - [Serializable()] - public class IntEncryptionInfo - { - public uint? Key { get; set; } - public string Scheme { get; set; } - public string Password { get; set; } - - public uint? GetKey () - { - if (null != Key && Key.HasValue) - return Key; - - if (!string.IsNullOrEmpty (Scheme)) - { - IntOpener.KeyData keydata; - if (IntOpener.KnownSchemes.TryGetValue (Scheme, out keydata)) - return keydata.Key; - } - - if (!string.IsNullOrEmpty (Password)) - return IntOpener.EncodePassPhrase (Password); - - return null; - } - } - - public class IntOptions : ResourceOptions - { - public IntEncryptionInfo EncryptionInfo { get; set; } - } - - [Export(typeof(ArchiveFormat))] - public class IntOpener : ArchiveFormat - { - public override string Tag { get { return "INT"; } } - public override string Description { get { return arcStrings.INTDescription; } } - public override uint Signature { get { return 0x0046494b; } } - public override bool IsHierarchic { get { return false; } } - public override bool CanCreate { get { return true; } } - - public override ArcFile TryOpen (ArcView file) - { - uint entry_count = file.View.ReadUInt32 (4); - if (0 == entry_count || 0 != ((entry_count - 1) >> 0x14)) - { - Trace.WriteLine (string.Format ("Invalid entry count ({0})", entry_count)); - return null; - } - if (file.View.AsciiEqual (8, "__key__.dat\x00")) - { - uint? key = QueryEncryptionInfo(); - if (null == key) - throw new UnknownEncryptionScheme(); - return OpenEncrypted (file, entry_count, key.Value); - } - - long current_offset = 8; - var dir = new List ((int)entry_count); - for (uint i = 0; i < entry_count; ++i) - { - string name = file.View.ReadString (current_offset, 0x40); - var entry = FormatCatalog.Instance.CreateEntry (name); - entry.Offset = file.View.ReadUInt32 (current_offset+0x40); - entry.Size = file.View.ReadUInt32 (current_offset+0x44); - if (!entry.CheckPlacement (file.MaxOffset)) - return null; - dir.Add (entry); - current_offset += 0x48; - } - return new ArcFile (file, this, dir); - } - - private ArcFile OpenEncrypted (ArcView file, uint entry_count, uint main_key) - { - if (1 == entry_count) - return null; // empty archive - long current_offset = 8; - var twister = new Twister(); - - // [@@L1] = 32-bit key - // [@@L1+4] = 0 if key is available, -1 otherwise - uint key_data = file.View.ReadUInt32 (current_offset+0x44); - uint twist_key = twister.Twist (key_data); - // [@@L0] = 32-bit twist key - byte[] blowfish_key = BitConverter.GetBytes (twist_key); - if (!BitConverter.IsLittleEndian) - Array.Reverse (blowfish_key); - - var blowfish = new Blowfish (blowfish_key); - var dir = new List ((int)entry_count-1); - byte[] name_info = new byte[0x40]; - for (uint i = 1; i < entry_count; ++i) - { - current_offset += 0x48; - file.View.Read (current_offset, name_info, 0, 0x40); - uint eax = file.View.ReadUInt32 (current_offset+0x40); - uint edx = file.View.ReadUInt32 (current_offset+0x44); - eax += i; - blowfish.Decipher (ref eax, ref edx); - uint key = twister.Twist (main_key + i); - string name = DecipherName (name_info, key); - - var entry = FormatCatalog.Instance.CreateEntry (name); - entry.Offset = eax; - entry.Size = edx; - if (!entry.CheckPlacement (file.MaxOffset)) - return null; - dir.Add (entry); - } - return new FrontwingArchive (file, this, dir, blowfish); - } - - private Stream OpenEncryptedEntry (FrontwingArchive arc, Entry entry) - { - using (var view = arc.File.CreateViewAccessor (entry.Offset, entry.Size)) - { - byte[] data = new byte[entry.Size]; - // below is supposedly faster version of - //arc.File.View.Read (entry.Offset, data, 0, entry.Size); - unsafe - { - byte* ptr = view.GetPointer (entry.Offset); - try { - Marshal.Copy (new IntPtr(ptr), data, 0, data.Length); - } finally { - view.SafeMemoryMappedViewHandle.ReleasePointer(); - } - } - arc.Encryption.Decipher (data, data.Length/8*8); - return new MemoryStream (data, false); - } - } - - public override Stream OpenEntry (ArcFile arc, Entry entry) - { - if (arc is FrontwingArchive) - return OpenEncryptedEntry (arc as FrontwingArchive, entry); - else - return base.OpenEntry (arc, entry); - } - - public string DecipherName (byte[] name, uint key) - { - key += (key >> 8) + (key >> 16) + (key >> 24); - key &= 0xff; - key %= 0x34; - int count = 0; - for (int i = 0; i < name.Length; ++i) - { - byte al = name[i]; - if (0 == al) - break; - byte bl = (byte)key; - ++count; - uint edx = al; - al |= 0x20; - al -= 0x61; - if (al < 0x1a) - { - if (0 != (edx & 0x20)) - al += 0x1a; - al = (byte)~al; - al += 0x34; - if (al >= bl) - al -= bl; - else - al = (byte)(al - bl + 0x34); - if (al >= 0x1a) - al += 6; - al += 0x41; - name[i] = al; - } - ++key; - if (0x34 == key) - key = 0; - } - return Encodings.cp932.GetString (name, 0, count); - } - - class Twister - { - const uint TwisterLength = 0x270; - uint[] m_twister = new uint[TwisterLength]; - uint m_twister_pos = 0; - - public uint Twist (uint key) - { - Init (key); - return Next(); - } - - public void Init (uint key) - { - uint edx = key; - for (int i = 0; i < TwisterLength; ++i) - { - uint ecx = edx * 0x10dcd + 1; - m_twister[i] = (edx & 0xffff0000) | (ecx >> 16); - edx *= 0x1C587629; - edx += 0x10dce; - } - m_twister_pos = 0; - } - - public uint Next () - { - uint ecx = m_twister[m_twister_pos]; - uint edx = m_twister_pos + 1; - if (TwisterLength == edx) - edx = 0; - uint edi = m_twister[edx]; - edi = ((edi ^ ecx) & 0x7FFFFFFF) ^ ecx; - bool carry = 0 != (edi & 1); - edi >>= 1; - if (carry) - edi ^= 0x9908B0DF; - ecx = m_twister_pos + 0x18d; - if (ecx >= TwisterLength) - ecx -= TwisterLength; - edi ^= m_twister[ecx]; - m_twister[m_twister_pos] = edi; - m_twister_pos = edx; - uint eax = edi ^ (edi >> 11); - eax = ((eax & 0xFF3A58AD) << 7) ^ eax; - eax = ((eax & 0xFFFFDF8C) << 15) ^ eax; - eax = (eax >> 18) ^ eax; - return eax; - } - } - - public static uint EncodePassPhrase (string password) - { - byte[] pass_bytes = Encodings.cp932.GetBytes (password); - uint key = 0xffffffff; - foreach (var c in pass_bytes) - { - uint val = (uint)c << 24; - key ^= val; - for (int i = 0; i < 8; ++i) - { - bool carry = 0 != (key & 0x80000000); - key <<= 1; - if (carry) - key ^= 0x4C11DB7; - } - key = ~key; - } - return key; - } - - public struct KeyData - { - public uint Key; - public string Passphrase; - } - - public static readonly Dictionary KnownSchemes = new Dictionary { - { "Grisaia no Kajitsu", new KeyData { Key=0x1DAD9120, Passphrase="FW-6JD55162" }}, - { "Shukufuku no Campanella", new KeyData { Key=0x4260E643, Passphrase="CAMPANELLA" }}, - { "Makai Tenshi Djibril -Episode 4-", new KeyData { Key=0xA5A166AA, Passphrase="FW_MAKAI-TENSHI_DJIBRIL4" }}, - { "Sengoku Tenshi Djibril (trial)", new KeyData { Key=0xef870610, Passphrase="FW-8O9B6WDS" }}, - }; - - public override ResourceOptions GetDefaultOptions () - { - return new IntOptions { - EncryptionInfo = Settings.Default.INTEncryption ?? new IntEncryptionInfo(), - }; - } - - public override ResourceOptions GetOptions (object w) - { - var widget = w as GUI.WidgetINT; - if (null != widget) - { - Settings.Default.INTEncryption = widget.Info; - return new IntOptions { EncryptionInfo = widget.Info }; - } - return this.GetDefaultOptions(); - } - - public override object GetAccessWidget () - { - return new GUI.WidgetINT (); - } - - public override object GetCreationWidget () - { - return new GUI.CreateINTWidget(); - } - - uint? QueryEncryptionInfo () - { - var options = Query (arcStrings.INTNotice); - return options.EncryptionInfo.GetKey(); - } - - public override void Create (Stream output, IEnumerable list, ResourceOptions options, - EntryCallback callback) - { - int file_count = list.Count(); - if (null != callback) - callback (file_count+2, null, null); - int callback_count = 0; - using (var writer = new BinaryWriter (output, Encoding.ASCII, true)) - { - writer.Write (Signature); - writer.Write (file_count); - long dir_offset = output.Position; - - var encoding = Encodings.cp932.WithFatalFallback(); - byte[] name_buf = new byte[0x40]; - int previous_size = 0; - - if (null != callback) - callback (callback_count++, null, arcStrings.MsgWritingIndex); - - // 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 (8, SeekOrigin.Current); - } - - // now, write files and remember offset/sizes - long current_offset = output.Position; - 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 || current_offset + size > uint.MaxValue) - throw new FileSizeException(); - current_offset += (uint)size; - entry.Size = (uint)size; - input.CopyTo (output); - } - } - - if (null != callback) - callback (callback_count++, null, arcStrings.MsgUpdatingIndex); - - // at last, go back to directory and write offset/sizes - dir_offset += 0x40; - foreach (var entry in list) - { - writer.BaseStream.Position = dir_offset; - writer.Write ((uint)entry.Offset); - writer.Write (entry.Size); - dir_offset += 0x48; - } - } - } - } -} +//! \file ArcINT.cs +//! \date Fri Jul 11 09:32:36 2014 +//! \brief Frontwing games archive. +// +// 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.IO; +using System.Linq; +using System.Text; +using System.ComponentModel.Composition; +using System.Collections.Generic; +using System.Diagnostics; +using Simias.Encryption; +using System.Runtime.InteropServices; +using GameRes.Formats.Strings; +using GameRes.Formats.Properties; + +namespace GameRes.Formats.CatSystem +{ + public class FrontwingArchive : ArcFile + { + public readonly Blowfish Encryption; + + public FrontwingArchive (ArcView arc, ArchiveFormat impl, ICollection dir, Blowfish cipher) + : base (arc, impl, dir) + { + Encryption = cipher; + } + } + + [Serializable()] + public class IntEncryptionInfo + { + public uint? Key { get; set; } + public string Scheme { get; set; } + public string Password { get; set; } + + public uint? GetKey () + { + if (null != Key && Key.HasValue) + return Key; + + if (!string.IsNullOrEmpty (Scheme)) + { + IntOpener.KeyData keydata; + if (IntOpener.KnownSchemes.TryGetValue (Scheme, out keydata)) + return keydata.Key; + } + + if (!string.IsNullOrEmpty (Password)) + return IntOpener.EncodePassPhrase (Password); + + return null; + } + } + + public class IntOptions : ResourceOptions + { + public IntEncryptionInfo EncryptionInfo { get; set; } + } + + [Export(typeof(ArchiveFormat))] + public class IntOpener : ArchiveFormat + { + public override string Tag { get { return "INT"; } } + public override string Description { get { return arcStrings.INTDescription; } } + public override uint Signature { get { return 0x0046494b; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return true; } } + + public override ArcFile TryOpen (ArcView file) + { + uint entry_count = file.View.ReadUInt32 (4); + if (0 == entry_count || 0 != ((entry_count - 1) >> 0x14)) + { + Trace.WriteLine (string.Format ("Invalid entry count ({0})", entry_count)); + return null; + } + if (file.View.AsciiEqual (8, "__key__.dat\x00")) + { + uint? key = QueryEncryptionInfo(); + if (null == key) + throw new UnknownEncryptionScheme(); + return OpenEncrypted (file, entry_count, key.Value); + } + + long current_offset = 8; + var dir = new List ((int)entry_count); + for (uint i = 0; i < entry_count; ++i) + { + string name = file.View.ReadString (current_offset, 0x40); + var entry = FormatCatalog.Instance.CreateEntry (name); + entry.Offset = file.View.ReadUInt32 (current_offset+0x40); + entry.Size = file.View.ReadUInt32 (current_offset+0x44); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + current_offset += 0x48; + } + return new ArcFile (file, this, dir); + } + + private ArcFile OpenEncrypted (ArcView file, uint entry_count, uint main_key) + { + if (1 == entry_count) + return null; // empty archive + long current_offset = 8; + var twister = new Twister(); + + // [@@L1] = 32-bit key + // [@@L1+4] = 0 if key is available, -1 otherwise + uint key_data = file.View.ReadUInt32 (current_offset+0x44); + uint twist_key = twister.Twist (key_data); + // [@@L0] = 32-bit twist key + byte[] blowfish_key = BitConverter.GetBytes (twist_key); + if (!BitConverter.IsLittleEndian) + Array.Reverse (blowfish_key); + + var blowfish = new Blowfish (blowfish_key); + var dir = new List ((int)entry_count-1); + byte[] name_info = new byte[0x40]; + for (uint i = 1; i < entry_count; ++i) + { + current_offset += 0x48; + file.View.Read (current_offset, name_info, 0, 0x40); + uint eax = file.View.ReadUInt32 (current_offset+0x40); + uint edx = file.View.ReadUInt32 (current_offset+0x44); + eax += i; + blowfish.Decipher (ref eax, ref edx); + uint key = twister.Twist (main_key + i); + string name = DecipherName (name_info, key); + + var entry = FormatCatalog.Instance.CreateEntry (name); + entry.Offset = eax; + entry.Size = edx; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + return new FrontwingArchive (file, this, dir, blowfish); + } + + private Stream OpenEncryptedEntry (FrontwingArchive arc, Entry entry) + { + using (var view = arc.File.CreateViewAccessor (entry.Offset, entry.Size)) + { + byte[] data = new byte[entry.Size]; + // below is supposedly faster version of + //arc.File.View.Read (entry.Offset, data, 0, entry.Size); + unsafe + { + byte* ptr = view.GetPointer (entry.Offset); + try { + Marshal.Copy (new IntPtr(ptr), data, 0, data.Length); + } finally { + view.SafeMemoryMappedViewHandle.ReleasePointer(); + } + } + arc.Encryption.Decipher (data, data.Length/8*8); + return new MemoryStream (data, false); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (arc is FrontwingArchive) + return OpenEncryptedEntry (arc as FrontwingArchive, entry); + else + return base.OpenEntry (arc, entry); + } + + public string DecipherName (byte[] name, uint key) + { + key += (key >> 8) + (key >> 16) + (key >> 24); + key &= 0xff; + key %= 0x34; + int count = 0; + for (int i = 0; i < name.Length; ++i) + { + byte al = name[i]; + if (0 == al) + break; + byte bl = (byte)key; + ++count; + uint edx = al; + al |= 0x20; + al -= 0x61; + if (al < 0x1a) + { + if (0 != (edx & 0x20)) + al += 0x1a; + al = (byte)~al; + al += 0x34; + if (al >= bl) + al -= bl; + else + al = (byte)(al - bl + 0x34); + if (al >= 0x1a) + al += 6; + al += 0x41; + name[i] = al; + } + ++key; + if (0x34 == key) + key = 0; + } + return Encodings.cp932.GetString (name, 0, count); + } + + class Twister + { + const uint TwisterLength = 0x270; + uint[] m_twister = new uint[TwisterLength]; + uint m_twister_pos = 0; + + public uint Twist (uint key) + { + Init (key); + return Next(); + } + + public void Init (uint key) + { + uint edx = key; + for (int i = 0; i < TwisterLength; ++i) + { + uint ecx = edx * 0x10dcd + 1; + m_twister[i] = (edx & 0xffff0000) | (ecx >> 16); + edx *= 0x1C587629; + edx += 0x10dce; + } + m_twister_pos = 0; + } + + public uint Next () + { + uint ecx = m_twister[m_twister_pos]; + uint edx = m_twister_pos + 1; + if (TwisterLength == edx) + edx = 0; + uint edi = m_twister[edx]; + edi = ((edi ^ ecx) & 0x7FFFFFFF) ^ ecx; + bool carry = 0 != (edi & 1); + edi >>= 1; + if (carry) + edi ^= 0x9908B0DF; + ecx = m_twister_pos + 0x18d; + if (ecx >= TwisterLength) + ecx -= TwisterLength; + edi ^= m_twister[ecx]; + m_twister[m_twister_pos] = edi; + m_twister_pos = edx; + uint eax = edi ^ (edi >> 11); + eax = ((eax & 0xFF3A58AD) << 7) ^ eax; + eax = ((eax & 0xFFFFDF8C) << 15) ^ eax; + eax = (eax >> 18) ^ eax; + return eax; + } + } + + public static uint EncodePassPhrase (string password) + { + byte[] pass_bytes = Encodings.cp932.GetBytes (password); + uint key = 0xffffffff; + foreach (var c in pass_bytes) + { + uint val = (uint)c << 24; + key ^= val; + for (int i = 0; i < 8; ++i) + { + bool carry = 0 != (key & 0x80000000); + key <<= 1; + if (carry) + key ^= 0x4C11DB7; + } + key = ~key; + } + return key; + } + + public struct KeyData + { + public uint Key; + public string Passphrase; + } + + public static readonly Dictionary KnownSchemes = new Dictionary { + { "Grisaia no Kajitsu", new KeyData { Key=0x1DAD9120, Passphrase="FW-6JD55162" }}, + { "Shukufuku no Campanella", new KeyData { Key=0x4260E643, Passphrase="CAMPANELLA" }}, + { "Makai Tenshi Djibril -Episode 4-", new KeyData { Key=0xA5A166AA, Passphrase="FW_MAKAI-TENSHI_DJIBRIL4" }}, + { "Sengoku Tenshi Djibril (trial)", new KeyData { Key=0xef870610, Passphrase="FW-8O9B6WDS" }}, + }; + + public override ResourceOptions GetDefaultOptions () + { + return new IntOptions { + EncryptionInfo = Settings.Default.INTEncryption ?? new IntEncryptionInfo(), + }; + } + + public override ResourceOptions GetOptions (object w) + { + var widget = w as GUI.WidgetINT; + if (null != widget) + { + Settings.Default.INTEncryption = widget.Info; + return new IntOptions { EncryptionInfo = widget.Info }; + } + return this.GetDefaultOptions(); + } + + public override object GetAccessWidget () + { + return new GUI.WidgetINT (); + } + + public override object GetCreationWidget () + { + return new GUI.CreateINTWidget(); + } + + uint? QueryEncryptionInfo () + { + var options = Query (arcStrings.INTNotice); + return options.EncryptionInfo.GetKey(); + } + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + int file_count = list.Count(); + if (null != callback) + callback (file_count+2, null, null); + int callback_count = 0; + using (var writer = new BinaryWriter (output, Encoding.ASCII, true)) + { + writer.Write (Signature); + writer.Write (file_count); + long dir_offset = output.Position; + + var encoding = Encodings.cp932.WithFatalFallback(); + byte[] name_buf = new byte[0x40]; + int previous_size = 0; + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + + // 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 (8, SeekOrigin.Current); + } + + // now, write files and remember offset/sizes + long current_offset = output.Position; + 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 || current_offset + size > uint.MaxValue) + throw new FileSizeException(); + current_offset += (uint)size; + entry.Size = (uint)size; + input.CopyTo (output); + } + } + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgUpdatingIndex); + + // at last, go back to directory and write offset/sizes + dir_offset += 0x40; + foreach (var entry in list) + { + writer.BaseStream.Position = dir_offset; + writer.Write ((uint)entry.Offset); + writer.Write (entry.Size); + dir_offset += 0x48; + } + } + } + } +} diff --git a/ArcFormats/CreateINTWidget.xaml b/ArcFormats/CatSystem/CreateINTWidget.xaml similarity index 100% rename from ArcFormats/CreateINTWidget.xaml rename to ArcFormats/CatSystem/CreateINTWidget.xaml diff --git a/ArcFormats/CreateINTWidget.xaml.cs b/ArcFormats/CatSystem/CreateINTWidget.xaml.cs similarity index 100% rename from ArcFormats/CreateINTWidget.xaml.cs rename to ArcFormats/CatSystem/CreateINTWidget.xaml.cs diff --git a/ArcFormats/ImageHG3.cs b/ArcFormats/CatSystem/ImageHG3.cs similarity index 97% rename from ArcFormats/ImageHG3.cs rename to ArcFormats/CatSystem/ImageHG3.cs index 6b201540..b05b9b0e 100644 --- a/ArcFormats/ImageHG3.cs +++ b/ArcFormats/CatSystem/ImageHG3.cs @@ -1,362 +1,362 @@ -//! \file ImageHG3.cs -//! \date Sat Jul 19 17:31:09 2014 -//! \brief Frontwing HG3 image 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.IO; -using System.ComponentModel.Composition; -using System.Windows.Media.Imaging; -using System.Windows.Media; -using GameRes.Compression; -using GameRes.Utility; - -namespace GameRes.Formats -{ - [Export(typeof(ImageFormat))] - public class Hg3Format : ImageFormat - { - public override string Tag { get { return "HG3"; } } - public override string Description { get { return "Frontwing proprietary image format"; } } - public override uint Signature { get { return 0x332d4748; } } - - public override ImageMetaData ReadMetaData (Stream stream) - { - var header = new byte[0x4c]; - if (0x4c != stream.Read (header, 0, header.Length)) - return null; - if (LittleEndian.ToUInt32 (header, 0) != Signature) - return null; - if (LittleEndian.ToUInt32 (header, 4) != 0x0c) - return null; - if (!Binary.AsciiEqual (header, 0x14, "stdinfo\0")) - return null; - if (0x38 != LittleEndian.ToUInt32 (header, 0x1c)) - return null; - if (0x20 != LittleEndian.ToUInt32 (header, 0x2c)) - return null; - uint width = LittleEndian.ToUInt32 (header, 0x24); // @@L0 - uint height = LittleEndian.ToUInt32 (header, 0x28); - int pos_x = LittleEndian.ToInt32 (header, 0x30); - int pos_y = LittleEndian.ToInt32 (header, 0x34); - pos_x -= LittleEndian.ToInt32 (header, 0x44); - pos_y -= LittleEndian.ToInt32 (header, 0x48); - - return new ImageMetaData - { - Width = width, - Height = height, - OffsetX = pos_x, - OffsetY = pos_y, - BPP = 32, - }; - } - - public override ImageData Read (Stream stream, ImageMetaData info) - { - stream.Position = 0x40; - bool flipped = 0 == (stream.ReadByte() & 1) || info.OffsetY < 0; - stream.Position = 0x4c; - var header = new byte[0x28]; - if (0x28 != stream.Read (header, 0, header.Length)) - return null; - if (!Binary.AsciiEqual (header, "img0000\0")) - return null; - uint data_size = LittleEndian.ToUInt32 (header, 0x0c); - if (data_size < 0x18) - return null; - if (info.Height != LittleEndian.ToUInt32 (header, 0x14)) - return null; - uint packed2_size = LittleEndian.ToUInt32 (header, 0x18); - uint unpacked2_size = LittleEndian.ToUInt32 (header, 0x1c); - uint packed1_size = LittleEndian.ToUInt32 (header, 0x20); - uint unpacked1_size = LittleEndian.ToUInt32 (header, 0x24); - if (packed2_size + packed1_size < packed2_size) - return null; - if (unpacked2_size + unpacked1_size < unpacked2_size) - return null; - - long data_pos = stream.Position; - using (var unpacked2 = ZLibCompressor.DeCompress (stream)) - { - stream.Position = data_pos + packed2_size; - using (var unpacked1 = ZLibCompressor.DeCompress (stream)) - { - var decoder = new Decoder (unpacked1.GetBuffer(), unpacked1_size, - unpacked2.GetBuffer(), unpacked2_size, - info.Width, info.Height); - decoder.Unpack(); - var pixels = decoder.Data; - int stride = (int)info.Width * 4; - var bitmap = BitmapSource.Create ((int)info.Width, (int)info.Height, 96, 96, - PixelFormats.Bgra32, null, pixels, stride); - if (flipped) - { - var flipped_bitmap = new TransformedBitmap(); - flipped_bitmap.BeginInit(); - flipped_bitmap.Source = bitmap; - flipped_bitmap.Transform = new ScaleTransform { ScaleY = -1 }; - flipped_bitmap.EndInit(); - bitmap = flipped_bitmap; - } - bitmap.Freeze(); - return new ImageData (bitmap, info); - } - } - } - - public override void Write (Stream file, ImageData image) - { - throw new NotImplementedException ("Hg3Format.Write not implemented"); - } - - class Decoder - { - byte[] m_in1; - byte[] m_in2; - byte[] m_image; - uint m_in1_size; - uint m_in2_size; - uint m_dst_size; - uint m_width; - uint m_height; - - public byte[] Data { get { return m_image; } } - - public Decoder (byte[] in1, uint in1_size, byte[] in2, uint in2_size, - uint width, uint height) - { - m_in1 = in1; - m_in1_size = in1_size; - m_in2 = in2; - m_in2_size = in2_size; - m_width = width; - m_height = height; - m_dst_size = width*height*4; - m_image = new byte[(int)m_dst_size]; - } - - uint esi; - uint edi; - uint eax; - uint ebx; - uint ecx; - uint edx; - - uint L0, L1, L2, L3, L4; - uint m_plane; - - public void Unpack () - { - m_plane = 0; - edi = 0; - esi = 0; - ebx = 0; - eax = m_in1_size; - L0 = m_in2_size; - L1 = 0; - bool skip_first = GetNextBit(); - Proc4(); - ecx = m_dst_size; - if (eax > m_dst_size) - throw new InvalidFormatException ("Underflow at Hg3Format.Decoder.Unpack()"); - m_dst_size -= eax; - ecx >>= 2; - L2 = eax; - L3 = ecx; - L4 = ecx; - for (;;) - { - if (!skip_first) - { - // @@1: - if (0 == L2) - break; - Proc4(); - ecx = eax; - if (ecx > L2) - throw new InvalidFormatException ("Overflow at Hg3Format.Decoder.Unpack()"); - L2 -= ecx; - eax = 0; - do - Proc2(); - while (0 != --ecx); - } - // @@1a: - if (0 == L2) - break; - Proc4(); - ecx = eax; - if (ecx > L2 || ecx > L0) - throw new InvalidFormatException ("Overflow (2) at Hg3Format.Decoder.Unpack()"); - L2 -= ecx; - L0 -= ecx; - do - { - eax = m_in2[L1++]; - Proc2(); - } - while (0 != --ecx); - skip_first = false; - } - // @@7: - ecx = m_dst_size; - esi = 0; - if (0 != ecx) - { - eax = 0; - do - Proc2(); - while (0 != --ecx); - } - Proc6 (m_width, m_height); - // @@9: - eax = 0; - edx = m_in1_size; - } - - void Proc2 () // @@2 - { - m_image[edi] = (byte)eax; - edi += 4; - if (0 == --L3) - { - edi = ++m_plane; - L3 = L4; - } - } - - bool GetNextBit () // @@3 - { - bool carry = 0 != (ebx & 1); - ebx >>= 1; - if (0 == ebx) - { - if (0 == m_in1_size--) - throw new InvalidFormatException ("Hg3Format.Decoder.Underflow at GetNextBit()"); - ebx = (uint)(m_in1[esi++] | 0x100); - carry = 0 != (ebx & 1); - ebx >>= 1; - } - return carry; - } - - void Proc4 () // @@4 - { - ecx = 0; - eax = 0; - do - { - if (ecx >= 0x20) - throw new InvalidFormatException ("Hg3Format.Decoder.Overflow at Proc4"); - ++ecx; - } - while (!GetNextBit()); - ++eax; - while (0 != --ecx) - { - eax += eax + (uint)(GetNextBit() ? 1 : 0); - } - } - - void Proc6 (uint width, uint height) - { - uint[] table = new uint[0x100]; - ecx = 0; - for (uint i = 0; i < 0x100; ++i) - { - eax = 0xffffffff; - edx = i; - do - { - eax >>= 2; - eax |= (edx & 3) << 30; - eax >>= 6; - edx >>= 2; - } - while (0 != (0x80 & eax)); - table[i] = eax; - } - ecx = width * height * 4; - for (uint i = 0; i < ecx; i += 4) - { - eax = m_image[i]; - edx = table[eax]; - edx <<= 2; - eax = m_image[i+1]; - edx += table[eax]; - edx <<= 2; - eax = m_image[i+2]; - edx += table[eax]; - edx <<= 2; - eax = m_image[i+3]; - edx += table[eax]; - m_image[i] = (byte)(edx); - m_image[i+1] = (byte)(edx >> 8); - m_image[i+2] = (byte)(edx >> 16); - m_image[i+3] = (byte)(edx >> 24); - } - edi = 0; - for (int i = 0; i < 4; ++i) - { - eax = m_image[edi]; - edx = (uint)(0 == (eax & 1) ? 0 : 0xff); - eax >>= 1; - eax ^= edx; - m_image[edi++] = (byte)eax; - } - ecx = width; - if (0 != --ecx) - { - ecx <<= 2; - do - { - eax = m_image[edi]; - edx = (uint)(0 == (eax & 1) ? 0 : 0xff); - eax >>= 1; - eax ^= edx; - eax += m_image[edi-4]; - m_image[edi++] = (byte)eax; - } - while (0 != --ecx); - } - ecx = height; - if (0 != --ecx) - { - uint stride = width*4; - ecx *= stride; - do - { - eax = m_image[edi]; - edx = (uint)(0 == (eax & 1) ? 0 : 0xff); - eax >>= 1; - eax ^= edx; - eax += m_image[edi-stride]; - m_image[edi++] = (byte)eax; - } - while (0 != --ecx); - } - } - } - } -} +//! \file ImageHG3.cs +//! \date Sat Jul 19 17:31:09 2014 +//! \brief Frontwing HG3 image 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.IO; +using System.ComponentModel.Composition; +using System.Windows.Media.Imaging; +using System.Windows.Media; +using GameRes.Compression; +using GameRes.Utility; + +namespace GameRes.Formats.CatSystem +{ + [Export(typeof(ImageFormat))] + public class Hg3Format : ImageFormat + { + public override string Tag { get { return "HG3"; } } + public override string Description { get { return "Frontwing proprietary image format"; } } + public override uint Signature { get { return 0x332d4748; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x4c]; + if (0x4c != stream.Read (header, 0, header.Length)) + return null; + if (LittleEndian.ToUInt32 (header, 0) != Signature) + return null; + if (LittleEndian.ToUInt32 (header, 4) != 0x0c) + return null; + if (!Binary.AsciiEqual (header, 0x14, "stdinfo\0")) + return null; + if (0x38 != LittleEndian.ToUInt32 (header, 0x1c)) + return null; + if (0x20 != LittleEndian.ToUInt32 (header, 0x2c)) + return null; + uint width = LittleEndian.ToUInt32 (header, 0x24); // @@L0 + uint height = LittleEndian.ToUInt32 (header, 0x28); + int pos_x = LittleEndian.ToInt32 (header, 0x30); + int pos_y = LittleEndian.ToInt32 (header, 0x34); + pos_x -= LittleEndian.ToInt32 (header, 0x44); + pos_y -= LittleEndian.ToInt32 (header, 0x48); + + return new ImageMetaData + { + Width = width, + Height = height, + OffsetX = pos_x, + OffsetY = pos_y, + BPP = 32, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + stream.Position = 0x40; + bool flipped = 0 == (stream.ReadByte() & 1) || info.OffsetY < 0; + stream.Position = 0x4c; + var header = new byte[0x28]; + if (0x28 != stream.Read (header, 0, header.Length)) + return null; + if (!Binary.AsciiEqual (header, "img0000\0")) + return null; + uint data_size = LittleEndian.ToUInt32 (header, 0x0c); + if (data_size < 0x18) + return null; + if (info.Height != LittleEndian.ToUInt32 (header, 0x14)) + return null; + uint packed2_size = LittleEndian.ToUInt32 (header, 0x18); + uint unpacked2_size = LittleEndian.ToUInt32 (header, 0x1c); + uint packed1_size = LittleEndian.ToUInt32 (header, 0x20); + uint unpacked1_size = LittleEndian.ToUInt32 (header, 0x24); + if (packed2_size + packed1_size < packed2_size) + return null; + if (unpacked2_size + unpacked1_size < unpacked2_size) + return null; + + long data_pos = stream.Position; + using (var unpacked2 = ZLibCompressor.DeCompress (stream)) + { + stream.Position = data_pos + packed2_size; + using (var unpacked1 = ZLibCompressor.DeCompress (stream)) + { + var decoder = new Decoder (unpacked1.GetBuffer(), unpacked1_size, + unpacked2.GetBuffer(), unpacked2_size, + info.Width, info.Height); + decoder.Unpack(); + var pixels = decoder.Data; + int stride = (int)info.Width * 4; + var bitmap = BitmapSource.Create ((int)info.Width, (int)info.Height, 96, 96, + PixelFormats.Bgra32, null, pixels, stride); + if (flipped) + { + var flipped_bitmap = new TransformedBitmap(); + flipped_bitmap.BeginInit(); + flipped_bitmap.Source = bitmap; + flipped_bitmap.Transform = new ScaleTransform { ScaleY = -1 }; + flipped_bitmap.EndInit(); + bitmap = flipped_bitmap; + } + bitmap.Freeze(); + return new ImageData (bitmap, info); + } + } + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("Hg3Format.Write not implemented"); + } + + class Decoder + { + byte[] m_in1; + byte[] m_in2; + byte[] m_image; + uint m_in1_size; + uint m_in2_size; + uint m_dst_size; + uint m_width; + uint m_height; + + public byte[] Data { get { return m_image; } } + + public Decoder (byte[] in1, uint in1_size, byte[] in2, uint in2_size, + uint width, uint height) + { + m_in1 = in1; + m_in1_size = in1_size; + m_in2 = in2; + m_in2_size = in2_size; + m_width = width; + m_height = height; + m_dst_size = width*height*4; + m_image = new byte[(int)m_dst_size]; + } + + uint esi; + uint edi; + uint eax; + uint ebx; + uint ecx; + uint edx; + + uint L0, L1, L2, L3, L4; + uint m_plane; + + public void Unpack () + { + m_plane = 0; + edi = 0; + esi = 0; + ebx = 0; + eax = m_in1_size; + L0 = m_in2_size; + L1 = 0; + bool skip_first = GetNextBit(); + Proc4(); + ecx = m_dst_size; + if (eax > m_dst_size) + throw new InvalidFormatException ("Underflow at Hg3Format.Decoder.Unpack()"); + m_dst_size -= eax; + ecx >>= 2; + L2 = eax; + L3 = ecx; + L4 = ecx; + for (;;) + { + if (!skip_first) + { + // @@1: + if (0 == L2) + break; + Proc4(); + ecx = eax; + if (ecx > L2) + throw new InvalidFormatException ("Overflow at Hg3Format.Decoder.Unpack()"); + L2 -= ecx; + eax = 0; + do + Proc2(); + while (0 != --ecx); + } + // @@1a: + if (0 == L2) + break; + Proc4(); + ecx = eax; + if (ecx > L2 || ecx > L0) + throw new InvalidFormatException ("Overflow (2) at Hg3Format.Decoder.Unpack()"); + L2 -= ecx; + L0 -= ecx; + do + { + eax = m_in2[L1++]; + Proc2(); + } + while (0 != --ecx); + skip_first = false; + } + // @@7: + ecx = m_dst_size; + esi = 0; + if (0 != ecx) + { + eax = 0; + do + Proc2(); + while (0 != --ecx); + } + Proc6 (m_width, m_height); + // @@9: + eax = 0; + edx = m_in1_size; + } + + void Proc2 () // @@2 + { + m_image[edi] = (byte)eax; + edi += 4; + if (0 == --L3) + { + edi = ++m_plane; + L3 = L4; + } + } + + bool GetNextBit () // @@3 + { + bool carry = 0 != (ebx & 1); + ebx >>= 1; + if (0 == ebx) + { + if (0 == m_in1_size--) + throw new InvalidFormatException ("Hg3Format.Decoder.Underflow at GetNextBit()"); + ebx = (uint)(m_in1[esi++] | 0x100); + carry = 0 != (ebx & 1); + ebx >>= 1; + } + return carry; + } + + void Proc4 () // @@4 + { + ecx = 0; + eax = 0; + do + { + if (ecx >= 0x20) + throw new InvalidFormatException ("Hg3Format.Decoder.Overflow at Proc4"); + ++ecx; + } + while (!GetNextBit()); + ++eax; + while (0 != --ecx) + { + eax += eax + (uint)(GetNextBit() ? 1 : 0); + } + } + + void Proc6 (uint width, uint height) + { + uint[] table = new uint[0x100]; + ecx = 0; + for (uint i = 0; i < 0x100; ++i) + { + eax = 0xffffffff; + edx = i; + do + { + eax >>= 2; + eax |= (edx & 3) << 30; + eax >>= 6; + edx >>= 2; + } + while (0 != (0x80 & eax)); + table[i] = eax; + } + ecx = width * height * 4; + for (uint i = 0; i < ecx; i += 4) + { + eax = m_image[i]; + edx = table[eax]; + edx <<= 2; + eax = m_image[i+1]; + edx += table[eax]; + edx <<= 2; + eax = m_image[i+2]; + edx += table[eax]; + edx <<= 2; + eax = m_image[i+3]; + edx += table[eax]; + m_image[i] = (byte)(edx); + m_image[i+1] = (byte)(edx >> 8); + m_image[i+2] = (byte)(edx >> 16); + m_image[i+3] = (byte)(edx >> 24); + } + edi = 0; + for (int i = 0; i < 4; ++i) + { + eax = m_image[edi]; + edx = (uint)(0 == (eax & 1) ? 0 : 0xff); + eax >>= 1; + eax ^= edx; + m_image[edi++] = (byte)eax; + } + ecx = width; + if (0 != --ecx) + { + ecx <<= 2; + do + { + eax = m_image[edi]; + edx = (uint)(0 == (eax & 1) ? 0 : 0xff); + eax >>= 1; + eax ^= edx; + eax += m_image[edi-4]; + m_image[edi++] = (byte)eax; + } + while (0 != --ecx); + } + ecx = height; + if (0 != --ecx) + { + uint stride = width*4; + ecx *= stride; + do + { + eax = m_image[edi]; + edx = (uint)(0 == (eax & 1) ? 0 : 0xff); + eax >>= 1; + eax ^= edx; + eax += m_image[edi-stride]; + m_image[edi++] = (byte)eax; + } + while (0 != --ecx); + } + } + } + } +} diff --git a/ArcFormats/WidgetINT.xaml b/ArcFormats/CatSystem/WidgetINT.xaml similarity index 96% rename from ArcFormats/WidgetINT.xaml rename to ArcFormats/CatSystem/WidgetINT.xaml index 7554f257..2bcb71cf 100644 --- a/ArcFormats/WidgetINT.xaml +++ b/ArcFormats/CatSystem/WidgetINT.xaml @@ -1,59 +1,59 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/ArcFormats/WidgetINT.xaml.cs b/ArcFormats/CatSystem/WidgetINT.xaml.cs similarity index 87% rename from ArcFormats/WidgetINT.xaml.cs rename to ArcFormats/CatSystem/WidgetINT.xaml.cs index dfa8b8e5..dc31a54f 100644 --- a/ArcFormats/WidgetINT.xaml.cs +++ b/ArcFormats/CatSystem/WidgetINT.xaml.cs @@ -1,107 +1,97 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -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; - -namespace GameRes.Formats.GUI -{ - /// - /// Interaction logic for WidgetINT.xaml - /// - public partial class WidgetINT : Grid - { - public WidgetINT () - { - InitializeComponent(); - this.DataContext = GameRes.Formats.Properties.Settings.Default.INTEncryption ?? new IntEncryptionInfo(); - - Passphrase.TextChanged += OnPassphraseChanged; - EncScheme.SelectionChanged += OnSchemeChanged; - } - - public IntEncryptionInfo Info { get { return this.DataContext as IntEncryptionInfo; } } - - void OnPasskeyChanged (object sender, TextChangedEventArgs e) - { - } - - void OnPassphraseChanged (object sender, TextChangedEventArgs e) - { - var widget = sender as TextBox; - uint key = IntOpener.EncodePassPhrase (widget.Text); - Passkey.Text = key.ToString ("X8"); - } - - void OnSchemeChanged (object sender, SelectionChangedEventArgs e) - { - var widget = sender as ComboBox; - IntOpener.KeyData keydata; - if (IntOpener.KnownSchemes.TryGetValue (widget.SelectedItem as string, out keydata)) - { - Passphrase.TextChanged -= OnPassphraseChanged; - try - { - Passphrase.Text = keydata.Passphrase; - Passkey.Text = keydata.Key.ToString ("X8"); - } - finally - { - Passphrase.TextChanged += OnPassphraseChanged; - } - } - } - } - - [ValueConversion(typeof(uint?), typeof(string))] - public class KeyConverter : IValueConverter - { - public object Convert (object value, Type targetType, object parameter, CultureInfo culture) - { - uint? key = (uint?)value; - return null != key ? key.Value.ToString ("X") : ""; - } - - public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) - { - string strValue = value as string; - uint result_key; - if (uint.TryParse(strValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result_key)) - return new uint? (result_key); - else - return null; - } - } - - public class PasskeyRule : ValidationRule - { - public PasskeyRule() - { - } - - public override ValidationResult Validate (object value, CultureInfo cultureInfo) - { - uint key = 0; - try - { - if (((string)value).Length > 0) - key = UInt32.Parse ((string)value, NumberStyles.HexNumber); - } - catch - { - return new ValidationResult (false, Strings.arcStrings.INTKeyRequirement); - } - return new ValidationResult (true, null); - } - } -} +using System; +using System.Globalization; +using System.Windows.Controls; +using System.Windows.Data; +using GameRes.Formats.CatSystem; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetINT.xaml + /// + public partial class WidgetINT : Grid + { + public WidgetINT () + { + InitializeComponent(); + this.DataContext = GameRes.Formats.Properties.Settings.Default.INTEncryption ?? new IntEncryptionInfo(); + + Passphrase.TextChanged += OnPassphraseChanged; + EncScheme.SelectionChanged += OnSchemeChanged; + } + + public IntEncryptionInfo Info { get { return this.DataContext as IntEncryptionInfo; } } + + void OnPasskeyChanged (object sender, TextChangedEventArgs e) + { + } + + void OnPassphraseChanged (object sender, TextChangedEventArgs e) + { + var widget = sender as TextBox; + uint key = IntOpener.EncodePassPhrase (widget.Text); + Passkey.Text = key.ToString ("X8"); + } + + void OnSchemeChanged (object sender, SelectionChangedEventArgs e) + { + var widget = sender as ComboBox; + IntOpener.KeyData keydata; + if (IntOpener.KnownSchemes.TryGetValue (widget.SelectedItem as string, out keydata)) + { + Passphrase.TextChanged -= OnPassphraseChanged; + try + { + Passphrase.Text = keydata.Passphrase; + Passkey.Text = keydata.Key.ToString ("X8"); + } + finally + { + Passphrase.TextChanged += OnPassphraseChanged; + } + } + } + } + + [ValueConversion(typeof(uint?), typeof(string))] + public class KeyConverter : IValueConverter + { + public object Convert (object value, Type targetType, object parameter, CultureInfo culture) + { + uint? key = (uint?)value; + return null != key ? key.Value.ToString ("X") : ""; + } + + public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) + { + string strValue = value as string; + uint result_key; + if (uint.TryParse(strValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result_key)) + return new uint? (result_key); + else + return null; + } + } + + public class PasskeyRule : ValidationRule + { + public PasskeyRule() + { + } + + public override ValidationResult Validate (object value, CultureInfo cultureInfo) + { + uint key = 0; + try + { + if (((string)value).Length > 0) + key = UInt32.Parse ((string)value, NumberStyles.HexNumber); + } + catch + { + return new ValidationResult (false, Strings.arcStrings.INTKeyRequirement); + } + return new ValidationResult (true, null); + } + } +} diff --git a/ArcFormats/ArcCherry.cs b/ArcFormats/Cherry/ArcCherry.cs similarity index 100% rename from ArcFormats/ArcCherry.cs rename to ArcFormats/Cherry/ArcCherry.cs diff --git a/ArcFormats/ImageGRP.cs b/ArcFormats/Cherry/ImageGRP.cs similarity index 100% rename from ArcFormats/ImageGRP.cs rename to ArcFormats/Cherry/ImageGRP.cs diff --git a/ArcFormats/ArcCircus.cs b/ArcFormats/Circus/ArcCircus.cs similarity index 100% rename from ArcFormats/ArcCircus.cs rename to ArcFormats/Circus/ArcCircus.cs diff --git a/ArcFormats/AudioPCM.cs b/ArcFormats/Circus/AudioPCM.cs similarity index 100% rename from ArcFormats/AudioPCM.cs rename to ArcFormats/Circus/AudioPCM.cs diff --git a/ArcFormats/ImageCRX.cs b/ArcFormats/Circus/ImageCRX.cs similarity index 100% rename from ArcFormats/ImageCRX.cs rename to ArcFormats/Circus/ImageCRX.cs diff --git a/ArcFormats/ArcPCK.cs b/ArcFormats/Crowd/ArcPCK.cs similarity index 100% rename from ArcFormats/ArcPCK.cs rename to ArcFormats/Crowd/ArcPCK.cs diff --git a/ArcFormats/AudioEOG.cs b/ArcFormats/Crowd/AudioEOG.cs similarity index 100% rename from ArcFormats/AudioEOG.cs rename to ArcFormats/Crowd/AudioEOG.cs diff --git a/ArcFormats/ImageCWL.cs b/ArcFormats/Crowd/ImageCWL.cs similarity index 100% rename from ArcFormats/ImageCWL.cs rename to ArcFormats/Crowd/ImageCWL.cs diff --git a/ArcFormats/ImageCWP.cs b/ArcFormats/Crowd/ImageCWP.cs similarity index 100% rename from ArcFormats/ImageCWP.cs rename to ArcFormats/Crowd/ImageCWP.cs diff --git a/ArcFormats/ImageZBM.cs b/ArcFormats/Crowd/ImageZBM.cs similarity index 100% rename from ArcFormats/ImageZBM.cs rename to ArcFormats/Crowd/ImageZBM.cs diff --git a/ArcFormats/ArcDPK.cs b/ArcFormats/Dac/ArcDPK.cs similarity index 100% rename from ArcFormats/ArcDPK.cs rename to ArcFormats/Dac/ArcDPK.cs diff --git a/ArcFormats/ImageDGC.cs b/ArcFormats/Dac/ImageDGC.cs similarity index 100% rename from ArcFormats/ImageDGC.cs rename to ArcFormats/Dac/ImageDGC.cs diff --git a/ArcFormats/WidgetDPK.xaml b/ArcFormats/Dac/WidgetDPK.xaml similarity index 100% rename from ArcFormats/WidgetDPK.xaml rename to ArcFormats/Dac/WidgetDPK.xaml diff --git a/ArcFormats/WidgetDPK.xaml.cs b/ArcFormats/Dac/WidgetDPK.xaml.cs similarity index 100% rename from ArcFormats/WidgetDPK.xaml.cs rename to ArcFormats/Dac/WidgetDPK.xaml.cs diff --git a/ArcFormats/ArcEAGLS.cs b/ArcFormats/Eagls/ArcEAGLS.cs similarity index 100% rename from ArcFormats/ArcEAGLS.cs rename to ArcFormats/Eagls/ArcEAGLS.cs diff --git a/ArcFormats/ImageGR.cs b/ArcFormats/Eagls/ImageGR.cs similarity index 100% rename from ArcFormats/ImageGR.cs rename to ArcFormats/Eagls/ImageGR.cs diff --git a/ArcFormats/ArcNOA.cs b/ArcFormats/Entis/ArcNOA.cs similarity index 100% rename from ArcFormats/ArcNOA.cs rename to ArcFormats/Entis/ArcNOA.cs diff --git a/ArcFormats/AudioMIO.cs b/ArcFormats/Entis/AudioMIO.cs similarity index 100% rename from ArcFormats/AudioMIO.cs rename to ArcFormats/Entis/AudioMIO.cs diff --git a/ArcFormats/EriReader.cs b/ArcFormats/Entis/EriReader.cs similarity index 100% rename from ArcFormats/EriReader.cs rename to ArcFormats/Entis/EriReader.cs diff --git a/ArcFormats/ImageERI.cs b/ArcFormats/Entis/ImageERI.cs similarity index 100% rename from ArcFormats/ImageERI.cs rename to ArcFormats/Entis/ImageERI.cs diff --git a/ArcFormats/MioDecoder.cs b/ArcFormats/Entis/MioDecoder.cs similarity index 100% rename from ArcFormats/MioDecoder.cs rename to ArcFormats/Entis/MioDecoder.cs diff --git a/ArcFormats/WidgetNOA.xaml b/ArcFormats/Entis/WidgetNOA.xaml similarity index 100% rename from ArcFormats/WidgetNOA.xaml rename to ArcFormats/Entis/WidgetNOA.xaml diff --git a/ArcFormats/WidgetNOA.xaml.cs b/ArcFormats/Entis/WidgetNOA.xaml.cs similarity index 100% rename from ArcFormats/WidgetNOA.xaml.cs rename to ArcFormats/Entis/WidgetNOA.xaml.cs diff --git a/ArcFormats/ArcBGI.cs b/ArcFormats/Ethornell/ArcBGI.cs similarity index 100% rename from ArcFormats/ArcBGI.cs rename to ArcFormats/Ethornell/ArcBGI.cs diff --git a/ArcFormats/ImageBGI.cs b/ArcFormats/Ethornell/ImageBGI.cs similarity index 100% rename from ArcFormats/ImageBGI.cs rename to ArcFormats/Ethornell/ImageBGI.cs diff --git a/ArcFormats/ArcMRG.cs b/ArcFormats/FC01/ArcMRG.cs similarity index 100% rename from ArcFormats/ArcMRG.cs rename to ArcFormats/FC01/ArcMRG.cs diff --git a/ArcFormats/ImageACD.cs b/ArcFormats/FC01/ImageACD.cs similarity index 100% rename from ArcFormats/ImageACD.cs rename to ArcFormats/FC01/ImageACD.cs diff --git a/ArcFormats/ImageMCG.cs b/ArcFormats/FC01/ImageMCG.cs similarity index 100% rename from ArcFormats/ImageMCG.cs rename to ArcFormats/FC01/ImageMCG.cs diff --git a/ArcFormats/ArcBlackPackage.cs b/ArcFormats/Ffa/ArcBlackPackage.cs similarity index 100% rename from ArcFormats/ArcBlackPackage.cs rename to ArcFormats/Ffa/ArcBlackPackage.cs diff --git a/ArcFormats/ArcFFA.cs b/ArcFormats/Ffa/ArcFFA.cs similarity index 100% rename from ArcFormats/ArcFFA.cs rename to ArcFormats/Ffa/ArcFFA.cs diff --git a/ArcFormats/AudioWA1.cs b/ArcFormats/Ffa/AudioWA1.cs similarity index 100% rename from ArcFormats/AudioWA1.cs rename to ArcFormats/Ffa/AudioWA1.cs diff --git a/ArcFormats/ImagePT1.cs b/ArcFormats/Ffa/ImagePT1.cs similarity index 100% rename from ArcFormats/ImagePT1.cs rename to ArcFormats/Ffa/ImagePT1.cs diff --git a/ArcFormats/ArcPD.cs b/ArcFormats/FlyingShine/ArcPD.cs similarity index 100% rename from ArcFormats/ArcPD.cs rename to ArcFormats/FlyingShine/ArcPD.cs diff --git a/ArcFormats/CreatePDWidget.xaml b/ArcFormats/FlyingShine/CreatePDWidget.xaml similarity index 100% rename from ArcFormats/CreatePDWidget.xaml rename to ArcFormats/FlyingShine/CreatePDWidget.xaml diff --git a/ArcFormats/CreatePDWidget.xaml.cs b/ArcFormats/FlyingShine/CreatePDWidget.xaml.cs similarity index 100% rename from ArcFormats/CreatePDWidget.xaml.cs rename to ArcFormats/FlyingShine/CreatePDWidget.xaml.cs diff --git a/ArcFormats/ArcGCEX.cs b/ArcFormats/G2/ArcGCEX.cs similarity index 100% rename from ArcFormats/ArcGCEX.cs rename to ArcFormats/G2/ArcGCEX.cs diff --git a/ArcFormats/ImageBGRA.cs b/ArcFormats/G2/ImageBGRA.cs similarity index 100% rename from ArcFormats/ImageBGRA.cs rename to ArcFormats/G2/ImageBGRA.cs diff --git a/ArcFormats/ArcGsPack.cs b/ArcFormats/GsPack/ArcGsPack.cs similarity index 100% rename from ArcFormats/ArcGsPack.cs rename to ArcFormats/GsPack/ArcGsPack.cs diff --git a/ArcFormats/ImageGS.cs b/ArcFormats/GsPack/ImageGS.cs similarity index 100% rename from ArcFormats/ImageGS.cs rename to ArcFormats/GsPack/ImageGS.cs diff --git a/ArcFormats/ArcDRS.cs b/ArcFormats/Ikura/ArcDRS.cs similarity index 99% rename from ArcFormats/ArcDRS.cs rename to ArcFormats/Ikura/ArcDRS.cs index 96a8477b..cdba62cc 100644 --- a/ArcFormats/ArcDRS.cs +++ b/ArcFormats/Ikura/ArcDRS.cs @@ -33,7 +33,7 @@ using GameRes.Utility; using GameRes.Formats.Strings; using GameRes.Formats.Properties; -namespace GameRes.Formats.DRS +namespace GameRes.Formats.Ikura { [Export(typeof(ArchiveFormat))] public class DrsOpener : ArchiveFormat diff --git a/ArcFormats/ImageDRG.cs b/ArcFormats/Ikura/ImageDRG.cs similarity index 99% rename from ArcFormats/ImageDRG.cs rename to ArcFormats/Ikura/ImageDRG.cs index af94ff01..3b8e5c27 100644 --- a/ArcFormats/ImageDRG.cs +++ b/ArcFormats/Ikura/ImageDRG.cs @@ -34,7 +34,7 @@ using System.Windows.Media.Imaging; using GameRes.Compression; using GameRes.Utility; -namespace GameRes.Formats.DRS +namespace GameRes.Formats.Ikura { [Export(typeof(ImageFormat))] public class DrgFormat : ImageFormat diff --git a/ArcFormats/ImageGGP.cs b/ArcFormats/Ikura/ImageGGP.cs similarity index 99% rename from ArcFormats/ImageGGP.cs rename to ArcFormats/Ikura/ImageGGP.cs index c28d06ca..3a7ba78a 100644 --- a/ArcFormats/ImageGGP.cs +++ b/ArcFormats/Ikura/ImageGGP.cs @@ -28,7 +28,7 @@ using System.ComponentModel.Composition; using System.IO; using GameRes.Utility; -namespace GameRes.Formats.DRS +namespace GameRes.Formats.Ikura { internal class GgpMetaData : ImageMetaData { diff --git a/ArcFormats/WidgetISF.xaml b/ArcFormats/Ikura/WidgetISF.xaml similarity index 90% rename from ArcFormats/WidgetISF.xaml rename to ArcFormats/Ikura/WidgetISF.xaml index 477dcc2d..9a223a70 100644 --- a/ArcFormats/WidgetISF.xaml +++ b/ArcFormats/Ikura/WidgetISF.xaml @@ -1,7 +1,7 @@  m_segments = new List(); - - public bool IsEncrypted { get; set; } - public ICrypt Cipher { get; set; } - public ICollection Segments { get { return m_segments; } } - public uint Hash { get; set; } - } - - public class Xp3Options : ResourceOptions - { - public int Version { get; set; } - public ICrypt Scheme { get; set; } - public bool CompressIndex { get; set; } - public bool CompressContents { get; set; } - public bool RetainDirs { get; set; } - } - - // Archive version 1: encrypt file first, then calculate checksum - // version 2: calculate checksum, then encrypt - - [Export(typeof(ArchiveFormat))] - public class Xp3Opener : ArchiveFormat - { - public override string Tag { get { return "XP3"; } } - public override string Description { get { return arcStrings.XP3Description; } } - public override uint Signature { get { return 0x0d335058; } } - public override bool IsHierarchic { get { return true; } } - public override bool CanCreate { get { return true; } } - - public Xp3Opener () - { - Signatures = new uint[] { 0x0d335058, 0 }; - } - - public bool ForceEncryptionQuery = true; - - private static readonly ICrypt NoCryptAlgorithm = new NoCrypt(); - - public static readonly Dictionary KnownSchemes = new Dictionary { - { arcStrings.ArcNoEncryption, NoCryptAlgorithm }, - { "Cafe Sourire", new XorCrypt (0xcd) }, - { "Coμ", new ComyuCrypt() }, - { "Damegane", new DameganeCrypt() }, - { "Fate/hollow ataraxia", new FateHACrypt() }, - { "Fate/stay night", new FateCrypt() }, - { "Hime to Majin to Koi Suru Tamashii", new HashCrypt() }, - { "Imouto Style", new ImoutoStyleCrypt() }, - { "Okiba ga Nai!", new OkibaCrypt() }, - { "Ore no Saimin Fantasia", new SaiminCrypt() }, - { "Seirei Tenshou", new SeitenCrypt() }, - { "Swan Song", new SwanSongCrypt() }, - }; - - public override ArcFile TryOpen (ArcView file) - { - long base_offset = 0; - if (0x5a4d == file.View.ReadUInt16 (0)) // 'MZ' - base_offset = SkipExeHeader (file); - if (!file.View.AsciiEqual (base_offset, "XP3\x0d\x0a\x20\x0a\x1a\x8b\x67\x01")) - return null; - long dir_offset = base_offset + file.View.ReadInt64 (base_offset+0x0b); - if (dir_offset < 0x13 || dir_offset >= file.MaxOffset) - return null; - if (0x80 == file.View.ReadUInt32 (dir_offset)) - { - dir_offset = base_offset + file.View.ReadInt64 (dir_offset+9); - if (dir_offset < 0x13 || dir_offset >= file.MaxOffset) - return null; - } - int header_type = file.View.ReadByte (dir_offset); - if (0 != header_type && 1 != header_type) - return null; - - Stream header_stream; - if (0 == header_type) // read unpacked header - { - long header_size = file.View.ReadInt64 (dir_offset+1); - if (header_size > uint.MaxValue) - return null; - header_stream = file.CreateStream (dir_offset+9, (uint)header_size); - } - else // read packed header - { - long packed_size = file.View.ReadInt64 (dir_offset+1); - if (packed_size > uint.MaxValue) - return null; - long header_size = file.View.ReadInt64 (dir_offset+9); - using (var input = file.CreateStream (dir_offset+17, (uint)packed_size)) - header_stream = ZLibCompressor.DeCompress (input); - } - - var crypt_algorithm = new Lazy (QueryCryptAlgorithm); - - var dir = new List(); - dir_offset = 0; - using (var header = new BinaryReader (header_stream, Encoding.Unicode)) - { - while (-1 != header.PeekChar()) - { - uint entry_signature = header.ReadUInt32(); - if (0x656c6946 != entry_signature) // "File" - { - break; - } - long entry_size = header.ReadInt64(); - dir_offset += 12 + entry_size; - var entry = new Xp3Entry(); - while (entry_size > 0) - { - uint section = header.ReadUInt32(); - long section_size = header.ReadInt64(); - entry_size -= 12; - if (section_size > entry_size) - break; - entry_size -= section_size; - long next_section_pos = header.BaseStream.Position + section_size; - switch (section) - { - case 0x6f666e69: // "info" - { - if (entry.Size != 0 || !string.IsNullOrEmpty (entry.Name)) - { - goto NextEntry; // ambiguous entry, ignore - } - entry.IsEncrypted = 0 != header.ReadUInt32(); - long file_size = header.ReadInt64(); - long packed_size = header.ReadInt64(); - if (file_size >= uint.MaxValue || packed_size > uint.MaxValue || packed_size > file.MaxOffset) - { - goto NextEntry; - } - entry.IsPacked = file_size != packed_size; - entry.Size = (uint)packed_size; - entry.UnpackedSize = (uint)file_size; - - int name_size = header.ReadInt16(); - if (name_size > 0x100 || name_size <= 0) - { - goto NextEntry; - } - if (entry.IsEncrypted || ForceEncryptionQuery) - entry.Cipher = crypt_algorithm.Value; - else - entry.Cipher = NoCryptAlgorithm; - entry.IsEncrypted = entry.Cipher != NoCryptAlgorithm; - - char[] name = header.ReadChars (name_size); - entry.Name = NormalizePath (new string (name)); - entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name); - break; - } - case 0x6d676573: // "segm" - { - int segment_count = (int)(section_size / 0x1c); - if (segment_count > 0) - { - for (int i = 0; i < segment_count; ++i) - { - bool compressed = 0 != header.ReadInt32(); - long segment_offset = base_offset+header.ReadInt64(); - long segment_size = header.ReadInt64(); - long segment_packed_size = header.ReadInt64(); - if (segment_offset > file.MaxOffset || segment_packed_size > file.MaxOffset) - { - goto NextEntry; - } - var segment = new Xp3Segment { - IsCompressed = compressed, - Offset = segment_offset, - Size = (uint)segment_size, - PackedSize = (uint)segment_packed_size - }; - entry.Segments.Add (segment); - } - entry.Offset = entry.Segments.First().Offset; - } - break; - } - case 0x726c6461: // "adlr" - if (4 == section_size) - entry.Hash = header.ReadUInt32(); - break; - - default: // unknown section - break; - } - header.BaseStream.Position = next_section_pos; - } - if (!string.IsNullOrEmpty (entry.Name) && entry.Segments.Any()) - { - dir.Add (entry); - } -NextEntry: - header.BaseStream.Position = dir_offset; - } - } - return new ArcFile (file, this, dir); - } - - static readonly Regex PathRe = new Regex (@"[^\\/]+[\\/]\.\.[\\/]"); - - private static string NormalizePath (string filename) - { - return PathRe.Replace (filename, ""); - } - - private long SkipExeHeader (ArcView file) - { - long offset = 0x10; - long pe_offset = file.View.ReadUInt32 (0x3c); - if (pe_offset < file.MaxOffset && 0x4550 == file.View.ReadUInt32 (pe_offset)) // 'PE' - { - int opt_header = file.View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader - offset = file.View.ReadUInt32 (pe_offset+0x54); // SizeOfHeaders - long section_table = pe_offset+opt_header+0x18; - int count = file.View.ReadUInt16 (pe_offset+6); // NumberOfSections - if (section_table + 0x28*count < file.MaxOffset) - { - for (int i = 0; i < count; ++i) - { - uint size = file.View.ReadUInt32 (section_table+0x10); - uint addr = file.View.ReadUInt32 (section_table+0x14); - section_table += 0x28; - if (0 != size) - offset = Math.Max ((long)addr + size, offset); - } - } - } - offset = (offset + 0xf) & ~(long)0xf; - for (; offset < file.MaxOffset; offset += 0x10) - { - if (file.View.AsciiEqual (offset, "XP3\x0d\x0a\x20\x0a\x1a\x8b\x67\x01")) - return offset; - } - return 0; - } - - public override Stream OpenEntry (ArcFile arc, Entry entry) - { - var xp3_entry = entry as Xp3Entry; - if (null == xp3_entry) - return arc.File.CreateStream (entry.Offset, entry.Size); -// Trace.WriteLine (string.Format ("{0,-16} {3:X8} {1,11} {2,12}", xp3_entry.Name, -// xp3_entry.IsEncrypted ? "[encrypted]" : "", -// xp3_entry.Segments.First().IsCompressed ? "[compressed]" : "", -// xp3_entry.Hash)); - if (1 == xp3_entry.Segments.Count && !xp3_entry.IsEncrypted) - { - var segment = xp3_entry.Segments.First(); - if (segment.IsCompressed) - return new ZLibStream (arc.File.CreateStream (segment.Offset, segment.PackedSize), - CompressionMode.Decompress); - else - return arc.File.CreateStream (segment.Offset, segment.Size); - } - return new Xp3Stream (arc.File, xp3_entry); - } - - public override ResourceOptions GetDefaultOptions () - { - return new Xp3Options { - Version = Settings.Default.XP3Version, - Scheme = GetScheme (Settings.Default.XP3Scheme), - CompressIndex = Settings.Default.XP3CompressHeader, - CompressContents = Settings.Default.XP3CompressContents, - RetainDirs = Settings.Default.XP3RetainStructure, - }; - } - - public override object GetCreationWidget () - { - return new GUI.CreateXP3Widget(); - } - - public override object GetAccessWidget () - { - return new GUI.WidgetXP3(); - } - - ICrypt QueryCryptAlgorithm () - { - var options = Query (arcStrings.ArcEncryptedNotice); - return options.Scheme; - } - - public static ICrypt GetScheme (string scheme) - { - ICrypt algorithm; - if (string.IsNullOrEmpty (scheme) || !KnownSchemes.TryGetValue (scheme, out algorithm)) - algorithm = NoCryptAlgorithm; - return algorithm; - } - - static uint GetFileCheckSum (Stream src) - { - // compute file checksum via adler32. - // src's file pointer should be reset to zero. - var sum = new Adler32(); - byte[] buf = new byte[64*1024]; - for (;;) - { - int read = src.Read (buf, 0, buf.Length); - if (0 == read) break; - sum.Update (buf, 0, read); - } - return sum.Value; - } - - static readonly byte[] s_xp3_header = { - (byte)'X', (byte)'P', (byte)'3', 0x0d, 0x0a, 0x20, 0x0a, 0x1a, 0x8b, 0x67, 0x01 - }; - - public override void Create (Stream output, IEnumerable list, ResourceOptions options, - EntryCallback callback) - { - var xp3_options = GetOptions (options); - - ICrypt scheme = xp3_options.Scheme; - bool compress_index = xp3_options.CompressIndex; - bool compress_contents = xp3_options.CompressContents; - bool retain_dirs = xp3_options.RetainDirs; - - bool use_encryption = scheme != NoCryptAlgorithm; - - using (var writer = new BinaryWriter (output, Encoding.ASCII, true)) - { - writer.Write (s_xp3_header); - if (2 == xp3_options.Version) - { - writer.Write ((long)0x17); - writer.Write ((int)1); - writer.Write ((byte)0x80); - writer.Write ((long)0); - } - long index_pos_offset = writer.BaseStream.Position; - writer.BaseStream.Seek (8, SeekOrigin.Current); - - int callback_count = 0; - var used_names = new HashSet(); - var dir = new List(); - long current_offset = writer.BaseStream.Position; - foreach (var entry in list) - { - if (null != callback) - callback (callback_count++, entry, arcStrings.MsgAddingFile); - - string name = entry.Name; - if (!retain_dirs) - name = Path.GetFileName (name); - else - name = name.Replace (@"\", "/"); - if (!used_names.Add (name)) - { - Trace.WriteLine ("duplicate name", entry.Name); - continue; - } - - var xp3entry = new Xp3Entry { - Name = name, - IsEncrypted = use_encryption, - Cipher = scheme, - }; - bool compress = compress_contents && ShouldCompressFile (entry); - using (var file = File.Open (name, FileMode.Open, FileAccess.Read)) - { - if (!use_encryption || 0 == file.Length) - RawFileCopy (file, xp3entry, output, compress); - else - EncryptedFileCopy (file, xp3entry, output, compress); - } - - dir.Add (xp3entry); - } - - long index_pos = writer.BaseStream.Position; - writer.BaseStream.Position = index_pos_offset; - writer.Write (index_pos); - writer.BaseStream.Position = index_pos; - - using (var header = new BinaryWriter (new MemoryStream (dir.Count*0x58), Encoding.Unicode)) - { - if (null != callback) - callback (callback_count++, null, arcStrings.MsgWritingIndex); - - long dir_pos = 0; - foreach (var entry in dir) - { - header.BaseStream.Position = dir_pos; - header.Write ((uint)0x656c6946); // "File" - long header_size_pos = header.BaseStream.Position; - header.Write ((long)0); - header.Write ((uint)0x6f666e69); // "info" - header.Write ((long)(4+8+8+2 + entry.Name.Length*2)); - header.Write ((uint)(use_encryption ? 0x80000000 : 0)); - header.Write ((long)entry.UnpackedSize); - header.Write ((long)entry.Size); - - header.Write ((short)entry.Name.Length); - foreach (char c in entry.Name) - header.Write (c); - - header.Write ((uint)0x6d676573); // "segm" - header.Write ((long)0x1c); - var segment = entry.Segments.First(); - header.Write ((int)(segment.IsCompressed ? 1 : 0)); - header.Write ((long)segment.Offset); - header.Write ((long)segment.Size); - header.Write ((long)segment.PackedSize); - - header.Write ((uint)0x726c6461); // "adlr" - header.Write ((long)4); - header.Write ((uint)entry.Hash); - - dir_pos = header.BaseStream.Position; - long header_size = dir_pos - header_size_pos - 8; - header.BaseStream.Position = header_size_pos; - header.Write (header_size); - } - - header.BaseStream.Position = 0; - writer.Write (compress_index); - long unpacked_dir_size = header.BaseStream.Length; - if (compress_index) - { - if (null != callback) - callback (callback_count++, null, arcStrings.MsgCompressingIndex); - - long packed_dir_size_pos = writer.BaseStream.Position; - writer.Write ((long)0); - writer.Write (unpacked_dir_size); - - long dir_start = writer.BaseStream.Position; - using (var zstream = new ZLibStream (writer.BaseStream, CompressionMode.Compress, - CompressionLevel.Level9, true)) - header.BaseStream.CopyTo (zstream); - - long packed_dir_size = writer.BaseStream.Position - dir_start; - writer.BaseStream.Position = packed_dir_size_pos; - writer.Write (packed_dir_size); - } - else - { - writer.Write (unpacked_dir_size); - header.BaseStream.CopyTo (writer.BaseStream); - } - } - } - output.Seek (0, SeekOrigin.End); - } - - void RawFileCopy (FileStream file, Xp3Entry xp3entry, Stream output, bool compress) - { - if (file.Length > uint.MaxValue) - throw new FileSizeException(); - - uint unpacked_size = (uint)file.Length; - xp3entry.UnpackedSize = (uint)unpacked_size; - xp3entry.Size = (uint)unpacked_size; - compress = compress && unpacked_size > 0; - var segment = new Xp3Segment { - IsCompressed = compress, - Offset = output.Position, - Size = unpacked_size, - PackedSize = unpacked_size - }; - if (compress) - { - var start = output.Position; - using (var zstream = new ZLibStream (output, CompressionMode.Compress, CompressionLevel.Level9, true)) - { - xp3entry.Hash = CheckedCopy (file, zstream); - } - segment.PackedSize = (uint)(output.Position - start); - xp3entry.Size = segment.PackedSize; - } - else - { - xp3entry.Hash = CheckedCopy (file, output); - } - xp3entry.Segments.Add (segment); - } - - void EncryptedFileCopy (FileStream file, Xp3Entry xp3entry, Stream output, bool compress) - { - if (file.Length > int.MaxValue) - throw new FileSizeException(); - - using (var map = MemoryMappedFile.CreateFromFile (file, null, 0, - MemoryMappedFileAccess.Read, null, HandleInheritability.None, true)) - { - uint unpacked_size = (uint)file.Length; - xp3entry.UnpackedSize = (uint)unpacked_size; - xp3entry.Size = (uint)unpacked_size; - using (var view = map.CreateViewAccessor (0, unpacked_size, MemoryMappedFileAccess.Read)) - { - var segment = new Xp3Segment { - IsCompressed = compress, - Offset = output.Position, - Size = unpacked_size, - PackedSize = unpacked_size, - }; - xp3entry.Segments.Add (segment); - if (compress) - { - output = new ZLibStream (output, CompressionMode.Compress, CompressionLevel.Level9, true); - } - unsafe - { - byte[] read_buffer = new byte[81920]; - byte* ptr = view.GetPointer (0); - try - { - var checksum = new Adler32(); - bool hash_after_crypt = xp3entry.Cipher.HashAfterCrypt; - if (!hash_after_crypt) - xp3entry.Hash = checksum.Update (ptr, (int)unpacked_size); - int offset = 0; - int remaining = (int)unpacked_size; - while (remaining > 0) - { - int amount = Math.Min (remaining, read_buffer.Length); - remaining -= amount; - Marshal.Copy ((IntPtr)(ptr+offset), read_buffer, 0, amount); - xp3entry.Cipher.Encrypt (xp3entry, offset, read_buffer, 0, amount); - if (hash_after_crypt) - checksum.Update (read_buffer, 0, amount); - output.Write (read_buffer, 0, amount); - offset += amount; - } - if (hash_after_crypt) - xp3entry.Hash = checksum.Value; - } - finally - { - view.SafeMemoryMappedViewHandle.ReleasePointer(); - if (compress) - { - var dest = (output as ZLibStream).BaseStream; - output.Dispose(); - segment.PackedSize = (uint)(dest.Position - segment.Offset); - xp3entry.Size = segment.PackedSize; - } - } - } - } - } - } - - uint CheckedCopy (Stream src, Stream dst) - { - var checksum = new Adler32(); - var read_buffer = new byte[81920]; - for (;;) - { - int read = src.Read (read_buffer, 0, read_buffer.Length); - if (0 == read) - break; - checksum.Update (read_buffer, 0, read); - dst.Write (read_buffer, 0, read); - } - return checksum.Value; - } - - bool ShouldCompressFile (Entry entry) - { - if ("image" == entry.Type || "archive" == entry.Type) - return false; - if (entry.Name.EndsWith (".ogg", StringComparison.InvariantCultureIgnoreCase)) - return false; - return true; - } - } - - internal class Xp3Stream : Stream - { - ArcView m_file; - Xp3Entry m_entry; - IEnumerator m_segment; - Stream m_stream; - long m_offset = 0; - bool m_eof = false; - - public override bool CanRead { get { return m_stream != null; } } - public override bool CanSeek { get { return false; } } - public override bool CanWrite { get { return false; } } - public override long Length { get { return m_entry.UnpackedSize; } } - public override long Position - { - get { return m_offset; } - set { throw new NotSupportedException ("Xp3Stream.Position not supported."); } - } - - public Xp3Stream (ArcView file, Xp3Entry entry) - { - m_file = file; - m_entry = entry; - m_segment = entry.Segments.GetEnumerator(); - NextSegment(); - } - - private void NextSegment () - { - if (!m_segment.MoveNext()) - { - m_eof = true; - return; - } - if (null != m_stream) - m_stream.Dispose(); - var segment = m_segment.Current; - m_stream = m_file.CreateStream (segment.Offset, segment.Size); - if (segment.IsCompressed) - m_stream = new ZLibStream (m_stream, CompressionMode.Decompress); - } - - public override int Read (byte[] buffer, int offset, int count) - { - int total = 0; - while (!m_eof && count > 0) - { - int read = m_stream.Read (buffer, offset, count); - m_entry.Cipher.Decrypt (m_entry, m_offset, buffer, offset, read); - m_offset += read; - total += read; - offset += read; - count -= read; - if (0 != count) - NextSegment(); - } - return total; - } - - public override int ReadByte () - { - int b = -1; - while (!m_eof) - { - b = m_stream.ReadByte(); - if (-1 != b) - { - b = m_entry.Cipher.Decrypt (m_entry, m_offset++, (byte)b); - break; - } - NextSegment(); - } - return b; - } - - public override void Flush () - { - } - - public override long Seek (long offset, SeekOrigin origin) - { - throw new NotSupportedException ("Xp3Stream.Seek method is not supported"); - } - - public override void SetLength (long length) - { - throw new NotSupportedException ("Xp3Stream.SetLength method is not supported"); - } - - public override void Write (byte[] buffer, int offset, int count) - { - throw new NotSupportedException ("Xp3Stream.Write method is not supported"); - } - - public override void WriteByte (byte value) - { - throw new NotSupportedException("Xp3Stream.WriteByte method is not supported"); - } - - #region IDisposable Members - bool disposed = false; - protected override void Dispose (bool disposing) - { - if (!disposed) - { - if (disposing && null != m_stream) - { - m_stream.Dispose(); - } - disposed = true; - base.Dispose (disposing); - } - } - #endregion - } - - public abstract class ICrypt - { - /// - /// whether Adler32 checksum should be calculated after contents have been encrypted. - /// - public virtual bool HashAfterCrypt { get { return false; } } - - public virtual byte Decrypt (Xp3Entry entry, long offset, byte value) - { - byte[] buffer = new byte[1] { value }; - Decrypt (entry, offset, buffer, 0, 1); - return buffer[0]; - } - - public abstract void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count); - - public virtual void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - throw new NotImplementedException (arcStrings.MsgEncNotImplemented); - } - } - - public class NoCrypt : ICrypt - { - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - return value; - } - public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - return; - } - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - return; - } - } - - internal class FateCrypt : ICrypt - { - public override bool HashAfterCrypt { get { return true; } } - - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - byte result = (byte)(value ^ 0x36); - if (0x13 == offset) - result ^= 1; - else if (0x2ea29 == offset) - result ^= 3; - return result; - } - - public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - for (int i = 0; i < count; ++i) - { - values[pos+i] ^= 0x36; - } - if (offset > 0x2ea29) - return; - if (offset + count > 0x2ea29) - values[pos+0x2ea29-offset] ^= 3; - if (offset > 0x13) - return; - if (offset + count > 0x13) - values[pos+0x13-offset] ^= 1; - } - - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - Decrypt (entry, offset, values, pos, count); - } - } - - internal class HashCrypt : ICrypt - { - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - return (byte)(value ^ entry.Hash); - } - - public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - byte key = (byte)entry.Hash; - for (int i = 0; i < count; ++i) - { - values[pos+i] ^= key; - } - } - - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - Decrypt (entry, offset, values, pos, count); - } - } - - internal class XorCrypt : ICrypt - { - private byte m_key; - - public byte Key - { - get { return m_key; } - set { m_key = value; } - } - - public XorCrypt (uint key) - { - m_key = (byte)key; - } - - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - return (byte)(value ^ m_key); - } - - public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - for (int i = 0; i < count; ++i) - { - values[pos+i] ^= m_key; - } - } - - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - Decrypt (entry, offset, values, pos, count); - } - } - - internal class SwanSongCrypt : ICrypt - { - static private byte Adjust (uint hash, out int shift) - { - int cl = (int)(hash & 0xff); - if (0 == cl) cl = 0x0f; - shift = cl & 7; - int ch = (int)((hash >> 8) & 0xff); - if (0 == ch) ch = 0xf0; - return (byte)ch; - } - - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - int shift; - byte xor = Adjust (entry.Hash, out shift); - uint data = (uint)(value ^ xor); - return (byte)((data >> shift) | (data << (8 - shift))); - } - - public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - int shift; - byte xor = Adjust (entry.Hash, out shift); - for (int i = 0; i < count; ++i) - { - uint data = (uint)(values[pos+i] ^ xor); - values[pos+i] = (byte)((data >> shift) | (data << (8 - shift))); - } - } - - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - int shift; - byte xor = Adjust (entry.Hash, out shift); - for (int i = 0; i < count; ++i) - { - uint data = values[pos+i]; - data = (byte)((data << shift) | (data >> (8 - shift))); - values[pos+i] = (byte)(data ^ xor); - } - } - } - - internal class SeitenCrypt : ICrypt - { - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - uint key = entry.Hash ^ (uint)offset; - if (0 != (key & 2)) - { - int ecx = (int)key & 0x18; - value ^= (byte)((key >> ecx) | (key >> (ecx & 8))); - } - if (0 != (key & 4)) - { - value += (byte)key; - } - if (0 != (key & 8)) - { - value -= (byte)(key >> (int)(key & 0x10)); - } - return value; - } - - public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count) - { - for (int i = 0; i < count; ++i) - { - int shift; - uint key = entry.Hash ^ (uint)offset; - byte v = buffer[pos+i]; - if (0 != (key & 2)) - { - shift = (int)key & 0x18; - uint ebx = key >> shift; - shift &= 8; - v ^= (byte)(ebx | (key >> shift)); - } - if (0 != (key & 4)) - { - v += (byte)key; - } - if (0 != (key & 8)) - { - shift = (int)key & 0x10; - v -= (byte)(key >> shift); - } - buffer[pos+i] = v; - ++offset; - } - } - - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - for (int i = 0; i < count; ++i) - { - uint key = entry.Hash ^ (uint)offset; - if (0 != (key & 8)) - { - values[pos+i] += (byte)(key >> (int)(key & 0x10)); - } - if (0 != (key & 4)) - { - values[pos+i] -= (byte)key; - } - if (0 != (key & 2)) - { - int ecx = (int)key & 0x18; - values[pos+i] ^= (byte)((key >> ecx) | (key >> (ecx & 8))); - } - } - } - } - - internal class OkibaCrypt : ICrypt - { - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - if (offset < 0x65) - return (byte)(value ^ (byte)(entry.Hash >> 4)); - uint key = entry.Hash; - // 0,1,2,3 -> 1,0,3,2 - key = ((key & 0xff0000) << 8) | ((key & 0xff000000) >> 8) - | ((key & 0xff00) >> 8) | ((key & 0xff) << 8); - key >>= 8 * ((int)(offset - 0x65) & 3); - return (byte)(value ^ (byte)key); - } - - public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - int i = 0; - if (offset < 0x65) - { - uint key = entry.Hash >> 4; - int limit = Math.Min (count, (int)(0x65 - offset)); - for (; i < limit; ++i) - { - values[pos+i] ^= (byte)key; - ++offset; - } - } - if (i < count) - { - offset -= 0x65; - uint key = entry.Hash; - key = ((key & 0xff0000) << 8) | ((key & 0xff000000) >> 8) - | ((key & 0xff00) >> 8) | ((key & 0xff) << 8); - do - { - values[pos+i] ^= (byte)(key >> (8 * ((int)offset & 3))); - ++offset; - } - while (++i < count); - } - } - - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - Decrypt (entry, offset, values, pos, count); - } - } - - internal class SaiminCrypt : ICrypt - { - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - byte key = (byte)entry.Hash; - if (offset < 0x7B) - value ^= (byte)(21 * key); - else if (offset < 0xF6) - value += (byte)(-32 * key); - else if (offset < 0x171) - value ^= (byte)(43 * key); - else if (offset <= 0xffffffffL) - value += (byte)(-54 * key); - return value; - } - - public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - byte key = (byte)entry.Hash; - for (int i = 0; i < count && offset <= 0xffffffffL; ++i, ++offset) - { - if (offset < 0x7B) - values[pos+i] ^= (byte)(21 * key); - else if (offset < 0xF6) - values[pos+i] += (byte)(-32 * key); - else if (offset >= 0x171) - values[pos+i] += (byte)(-54 * key); - else - values[pos+i] ^= (byte)(43 * key); - } - } - - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - byte key = (byte)entry.Hash; - for (int i = 0; i < count && offset <= 0xffffffffL; ++i, ++offset) - { - if (offset < 0x7B) - values[pos+i] ^= (byte)(21 * key); - else if (offset < 0xF6) - values[pos+i] -= (byte)(-32 * key); - else if (offset >= 0x171) - values[pos+i] -= (byte)(-54 * key); - else - values[pos+i] ^= (byte)(43 * key); - } - } - } - - internal class DameganeCrypt : ICrypt - { - public override byte Decrypt (Xp3Entry entry, long offset, byte value) - { - if (0 != (offset & 1)) - return (byte)(value ^ entry.Hash); - else - return (byte)(value ^ offset); - } - - public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - for (int i = 0; i < count; ++i, ++offset) - { - if (0 != (offset & 1)) - values[pos+i] ^= (byte)entry.Hash; - else - values[pos+i] ^= (byte)offset; - } - } - - public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) - { - Decrypt (entry, offset, values, pos, count); - } - } -} +//! \file ArcXP3.cs +//! \date Wed Jul 16 13:58:17 2014 +//! \brief KiriKiri engine archive implementation. +// +// 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 +// 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.IO.MemoryMappedFiles; +using System.Text; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Runtime.InteropServices; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Diagnostics; +using GameRes.Compression; +using GameRes.Utility; +using GameRes.Formats.Strings; +using GameRes.Formats.Properties; + +namespace GameRes.Formats.KiriKiri +{ + public struct Xp3Segment + { + public bool IsCompressed; + public long Offset; + public uint Size; + public uint PackedSize; + } + + public class Xp3Entry : PackedEntry + { + List m_segments = new List(); + + public bool IsEncrypted { get; set; } + public ICrypt Cipher { get; set; } + public ICollection Segments { get { return m_segments; } } + public uint Hash { get; set; } + } + + public class Xp3Options : ResourceOptions + { + public int Version { get; set; } + public ICrypt Scheme { get; set; } + public bool CompressIndex { get; set; } + public bool CompressContents { get; set; } + public bool RetainDirs { get; set; } + } + + // Archive version 1: encrypt file first, then calculate checksum + // version 2: calculate checksum, then encrypt + + [Export(typeof(ArchiveFormat))] + public class Xp3Opener : ArchiveFormat + { + public override string Tag { get { return "XP3"; } } + public override string Description { get { return arcStrings.XP3Description; } } + public override uint Signature { get { return 0x0d335058; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return true; } } + + public Xp3Opener () + { + Signatures = new uint[] { 0x0d335058, 0 }; + } + + public bool ForceEncryptionQuery = true; + + private static readonly ICrypt NoCryptAlgorithm = new NoCrypt(); + + public static readonly Dictionary KnownSchemes = new Dictionary { + { arcStrings.ArcNoEncryption, NoCryptAlgorithm }, + { "Cafe Sourire", new XorCrypt (0xcd) }, + { "Coμ", new ComyuCrypt() }, + { "Damegane", new DameganeCrypt() }, + { "Fate/hollow ataraxia", new FateHACrypt() }, + { "Fate/stay night", new FateCrypt() }, + { "Hime to Majin to Koi Suru Tamashii", new HashCrypt() }, + { "Imouto Style", new ImoutoStyleCrypt() }, + { "Okiba ga Nai!", new OkibaCrypt() }, + { "Ore no Saimin Fantasia", new SaiminCrypt() }, + { "Seirei Tenshou", new SeitenCrypt() }, + { "Swan Song", new SwanSongCrypt() }, + }; + + public override ArcFile TryOpen (ArcView file) + { + long base_offset = 0; + if (0x5a4d == file.View.ReadUInt16 (0)) // 'MZ' + base_offset = SkipExeHeader (file); + if (!file.View.AsciiEqual (base_offset, "XP3\x0d\x0a\x20\x0a\x1a\x8b\x67\x01")) + return null; + long dir_offset = base_offset + file.View.ReadInt64 (base_offset+0x0b); + if (dir_offset < 0x13 || dir_offset >= file.MaxOffset) + return null; + if (0x80 == file.View.ReadUInt32 (dir_offset)) + { + dir_offset = base_offset + file.View.ReadInt64 (dir_offset+9); + if (dir_offset < 0x13 || dir_offset >= file.MaxOffset) + return null; + } + int header_type = file.View.ReadByte (dir_offset); + if (0 != header_type && 1 != header_type) + return null; + + Stream header_stream; + if (0 == header_type) // read unpacked header + { + long header_size = file.View.ReadInt64 (dir_offset+1); + if (header_size > uint.MaxValue) + return null; + header_stream = file.CreateStream (dir_offset+9, (uint)header_size); + } + else // read packed header + { + long packed_size = file.View.ReadInt64 (dir_offset+1); + if (packed_size > uint.MaxValue) + return null; + long header_size = file.View.ReadInt64 (dir_offset+9); + using (var input = file.CreateStream (dir_offset+17, (uint)packed_size)) + header_stream = ZLibCompressor.DeCompress (input); + } + + var crypt_algorithm = new Lazy (QueryCryptAlgorithm); + + var dir = new List(); + dir_offset = 0; + using (var header = new BinaryReader (header_stream, Encoding.Unicode)) + { + while (-1 != header.PeekChar()) + { + uint entry_signature = header.ReadUInt32(); + if (0x656c6946 != entry_signature) // "File" + { + break; + } + long entry_size = header.ReadInt64(); + dir_offset += 12 + entry_size; + var entry = new Xp3Entry(); + while (entry_size > 0) + { + uint section = header.ReadUInt32(); + long section_size = header.ReadInt64(); + entry_size -= 12; + if (section_size > entry_size) + break; + entry_size -= section_size; + long next_section_pos = header.BaseStream.Position + section_size; + switch (section) + { + case 0x6f666e69: // "info" + { + if (entry.Size != 0 || !string.IsNullOrEmpty (entry.Name)) + { + goto NextEntry; // ambiguous entry, ignore + } + entry.IsEncrypted = 0 != header.ReadUInt32(); + long file_size = header.ReadInt64(); + long packed_size = header.ReadInt64(); + if (file_size >= uint.MaxValue || packed_size > uint.MaxValue || packed_size > file.MaxOffset) + { + goto NextEntry; + } + entry.IsPacked = file_size != packed_size; + entry.Size = (uint)packed_size; + entry.UnpackedSize = (uint)file_size; + + int name_size = header.ReadInt16(); + if (name_size > 0x100 || name_size <= 0) + { + goto NextEntry; + } + if (entry.IsEncrypted || ForceEncryptionQuery) + entry.Cipher = crypt_algorithm.Value; + else + entry.Cipher = NoCryptAlgorithm; + entry.IsEncrypted = entry.Cipher != NoCryptAlgorithm; + + char[] name = header.ReadChars (name_size); + entry.Name = NormalizePath (new string (name)); + entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name); + break; + } + case 0x6d676573: // "segm" + { + int segment_count = (int)(section_size / 0x1c); + if (segment_count > 0) + { + for (int i = 0; i < segment_count; ++i) + { + bool compressed = 0 != header.ReadInt32(); + long segment_offset = base_offset+header.ReadInt64(); + long segment_size = header.ReadInt64(); + long segment_packed_size = header.ReadInt64(); + if (segment_offset > file.MaxOffset || segment_packed_size > file.MaxOffset) + { + goto NextEntry; + } + var segment = new Xp3Segment { + IsCompressed = compressed, + Offset = segment_offset, + Size = (uint)segment_size, + PackedSize = (uint)segment_packed_size + }; + entry.Segments.Add (segment); + } + entry.Offset = entry.Segments.First().Offset; + } + break; + } + case 0x726c6461: // "adlr" + if (4 == section_size) + entry.Hash = header.ReadUInt32(); + break; + + default: // unknown section + break; + } + header.BaseStream.Position = next_section_pos; + } + if (!string.IsNullOrEmpty (entry.Name) && entry.Segments.Any()) + { + dir.Add (entry); + } +NextEntry: + header.BaseStream.Position = dir_offset; + } + } + return new ArcFile (file, this, dir); + } + + static readonly Regex PathRe = new Regex (@"[^\\/]+[\\/]\.\.[\\/]"); + + private static string NormalizePath (string filename) + { + return PathRe.Replace (filename, ""); + } + + private long SkipExeHeader (ArcView file) + { + long offset = 0x10; + long pe_offset = file.View.ReadUInt32 (0x3c); + if (pe_offset < file.MaxOffset && 0x4550 == file.View.ReadUInt32 (pe_offset)) // 'PE' + { + int opt_header = file.View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader + offset = file.View.ReadUInt32 (pe_offset+0x54); // SizeOfHeaders + long section_table = pe_offset+opt_header+0x18; + int count = file.View.ReadUInt16 (pe_offset+6); // NumberOfSections + if (section_table + 0x28*count < file.MaxOffset) + { + for (int i = 0; i < count; ++i) + { + uint size = file.View.ReadUInt32 (section_table+0x10); + uint addr = file.View.ReadUInt32 (section_table+0x14); + section_table += 0x28; + if (0 != size) + offset = Math.Max ((long)addr + size, offset); + } + } + } + offset = (offset + 0xf) & ~(long)0xf; + for (; offset < file.MaxOffset; offset += 0x10) + { + if (file.View.AsciiEqual (offset, "XP3\x0d\x0a\x20\x0a\x1a\x8b\x67\x01")) + return offset; + } + return 0; + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var xp3_entry = entry as Xp3Entry; + if (null == xp3_entry) + return arc.File.CreateStream (entry.Offset, entry.Size); +// Trace.WriteLine (string.Format ("{0,-16} {3:X8} {1,11} {2,12}", xp3_entry.Name, +// xp3_entry.IsEncrypted ? "[encrypted]" : "", +// xp3_entry.Segments.First().IsCompressed ? "[compressed]" : "", +// xp3_entry.Hash)); + if (1 == xp3_entry.Segments.Count && !xp3_entry.IsEncrypted) + { + var segment = xp3_entry.Segments.First(); + if (segment.IsCompressed) + return new ZLibStream (arc.File.CreateStream (segment.Offset, segment.PackedSize), + CompressionMode.Decompress); + else + return arc.File.CreateStream (segment.Offset, segment.Size); + } + return new Xp3Stream (arc.File, xp3_entry); + } + + public override ResourceOptions GetDefaultOptions () + { + return new Xp3Options { + Version = Settings.Default.XP3Version, + Scheme = GetScheme (Settings.Default.XP3Scheme), + CompressIndex = Settings.Default.XP3CompressHeader, + CompressContents = Settings.Default.XP3CompressContents, + RetainDirs = Settings.Default.XP3RetainStructure, + }; + } + + public override object GetCreationWidget () + { + return new GUI.CreateXP3Widget(); + } + + public override object GetAccessWidget () + { + return new GUI.WidgetXP3(); + } + + ICrypt QueryCryptAlgorithm () + { + var options = Query (arcStrings.ArcEncryptedNotice); + return options.Scheme; + } + + public static ICrypt GetScheme (string scheme) + { + ICrypt algorithm; + if (string.IsNullOrEmpty (scheme) || !KnownSchemes.TryGetValue (scheme, out algorithm)) + algorithm = NoCryptAlgorithm; + return algorithm; + } + + static uint GetFileCheckSum (Stream src) + { + // compute file checksum via adler32. + // src's file pointer should be reset to zero. + var sum = new Adler32(); + byte[] buf = new byte[64*1024]; + for (;;) + { + int read = src.Read (buf, 0, buf.Length); + if (0 == read) break; + sum.Update (buf, 0, read); + } + return sum.Value; + } + + static readonly byte[] s_xp3_header = { + (byte)'X', (byte)'P', (byte)'3', 0x0d, 0x0a, 0x20, 0x0a, 0x1a, 0x8b, 0x67, 0x01 + }; + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + var xp3_options = GetOptions (options); + + ICrypt scheme = xp3_options.Scheme; + bool compress_index = xp3_options.CompressIndex; + bool compress_contents = xp3_options.CompressContents; + bool retain_dirs = xp3_options.RetainDirs; + + bool use_encryption = scheme != NoCryptAlgorithm; + + using (var writer = new BinaryWriter (output, Encoding.ASCII, true)) + { + writer.Write (s_xp3_header); + if (2 == xp3_options.Version) + { + writer.Write ((long)0x17); + writer.Write ((int)1); + writer.Write ((byte)0x80); + writer.Write ((long)0); + } + long index_pos_offset = writer.BaseStream.Position; + writer.BaseStream.Seek (8, SeekOrigin.Current); + + int callback_count = 0; + var used_names = new HashSet(); + var dir = new List(); + long current_offset = writer.BaseStream.Position; + foreach (var entry in list) + { + if (null != callback) + callback (callback_count++, entry, arcStrings.MsgAddingFile); + + string name = entry.Name; + if (!retain_dirs) + name = Path.GetFileName (name); + else + name = name.Replace (@"\", "/"); + if (!used_names.Add (name)) + { + Trace.WriteLine ("duplicate name", entry.Name); + continue; + } + + var xp3entry = new Xp3Entry { + Name = name, + IsEncrypted = use_encryption, + Cipher = scheme, + }; + bool compress = compress_contents && ShouldCompressFile (entry); + using (var file = File.Open (name, FileMode.Open, FileAccess.Read)) + { + if (!use_encryption || 0 == file.Length) + RawFileCopy (file, xp3entry, output, compress); + else + EncryptedFileCopy (file, xp3entry, output, compress); + } + + dir.Add (xp3entry); + } + + long index_pos = writer.BaseStream.Position; + writer.BaseStream.Position = index_pos_offset; + writer.Write (index_pos); + writer.BaseStream.Position = index_pos; + + using (var header = new BinaryWriter (new MemoryStream (dir.Count*0x58), Encoding.Unicode)) + { + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + + long dir_pos = 0; + foreach (var entry in dir) + { + header.BaseStream.Position = dir_pos; + header.Write ((uint)0x656c6946); // "File" + long header_size_pos = header.BaseStream.Position; + header.Write ((long)0); + header.Write ((uint)0x6f666e69); // "info" + header.Write ((long)(4+8+8+2 + entry.Name.Length*2)); + header.Write ((uint)(use_encryption ? 0x80000000 : 0)); + header.Write ((long)entry.UnpackedSize); + header.Write ((long)entry.Size); + + header.Write ((short)entry.Name.Length); + foreach (char c in entry.Name) + header.Write (c); + + header.Write ((uint)0x6d676573); // "segm" + header.Write ((long)0x1c); + var segment = entry.Segments.First(); + header.Write ((int)(segment.IsCompressed ? 1 : 0)); + header.Write ((long)segment.Offset); + header.Write ((long)segment.Size); + header.Write ((long)segment.PackedSize); + + header.Write ((uint)0x726c6461); // "adlr" + header.Write ((long)4); + header.Write ((uint)entry.Hash); + + dir_pos = header.BaseStream.Position; + long header_size = dir_pos - header_size_pos - 8; + header.BaseStream.Position = header_size_pos; + header.Write (header_size); + } + + header.BaseStream.Position = 0; + writer.Write (compress_index); + long unpacked_dir_size = header.BaseStream.Length; + if (compress_index) + { + if (null != callback) + callback (callback_count++, null, arcStrings.MsgCompressingIndex); + + long packed_dir_size_pos = writer.BaseStream.Position; + writer.Write ((long)0); + writer.Write (unpacked_dir_size); + + long dir_start = writer.BaseStream.Position; + using (var zstream = new ZLibStream (writer.BaseStream, CompressionMode.Compress, + CompressionLevel.Level9, true)) + header.BaseStream.CopyTo (zstream); + + long packed_dir_size = writer.BaseStream.Position - dir_start; + writer.BaseStream.Position = packed_dir_size_pos; + writer.Write (packed_dir_size); + } + else + { + writer.Write (unpacked_dir_size); + header.BaseStream.CopyTo (writer.BaseStream); + } + } + } + output.Seek (0, SeekOrigin.End); + } + + void RawFileCopy (FileStream file, Xp3Entry xp3entry, Stream output, bool compress) + { + if (file.Length > uint.MaxValue) + throw new FileSizeException(); + + uint unpacked_size = (uint)file.Length; + xp3entry.UnpackedSize = (uint)unpacked_size; + xp3entry.Size = (uint)unpacked_size; + compress = compress && unpacked_size > 0; + var segment = new Xp3Segment { + IsCompressed = compress, + Offset = output.Position, + Size = unpacked_size, + PackedSize = unpacked_size + }; + if (compress) + { + var start = output.Position; + using (var zstream = new ZLibStream (output, CompressionMode.Compress, CompressionLevel.Level9, true)) + { + xp3entry.Hash = CheckedCopy (file, zstream); + } + segment.PackedSize = (uint)(output.Position - start); + xp3entry.Size = segment.PackedSize; + } + else + { + xp3entry.Hash = CheckedCopy (file, output); + } + xp3entry.Segments.Add (segment); + } + + void EncryptedFileCopy (FileStream file, Xp3Entry xp3entry, Stream output, bool compress) + { + if (file.Length > int.MaxValue) + throw new FileSizeException(); + + using (var map = MemoryMappedFile.CreateFromFile (file, null, 0, + MemoryMappedFileAccess.Read, null, HandleInheritability.None, true)) + { + uint unpacked_size = (uint)file.Length; + xp3entry.UnpackedSize = (uint)unpacked_size; + xp3entry.Size = (uint)unpacked_size; + using (var view = map.CreateViewAccessor (0, unpacked_size, MemoryMappedFileAccess.Read)) + { + var segment = new Xp3Segment { + IsCompressed = compress, + Offset = output.Position, + Size = unpacked_size, + PackedSize = unpacked_size, + }; + xp3entry.Segments.Add (segment); + if (compress) + { + output = new ZLibStream (output, CompressionMode.Compress, CompressionLevel.Level9, true); + } + unsafe + { + byte[] read_buffer = new byte[81920]; + byte* ptr = view.GetPointer (0); + try + { + var checksum = new Adler32(); + bool hash_after_crypt = xp3entry.Cipher.HashAfterCrypt; + if (!hash_after_crypt) + xp3entry.Hash = checksum.Update (ptr, (int)unpacked_size); + int offset = 0; + int remaining = (int)unpacked_size; + while (remaining > 0) + { + int amount = Math.Min (remaining, read_buffer.Length); + remaining -= amount; + Marshal.Copy ((IntPtr)(ptr+offset), read_buffer, 0, amount); + xp3entry.Cipher.Encrypt (xp3entry, offset, read_buffer, 0, amount); + if (hash_after_crypt) + checksum.Update (read_buffer, 0, amount); + output.Write (read_buffer, 0, amount); + offset += amount; + } + if (hash_after_crypt) + xp3entry.Hash = checksum.Value; + } + finally + { + view.SafeMemoryMappedViewHandle.ReleasePointer(); + if (compress) + { + var dest = (output as ZLibStream).BaseStream; + output.Dispose(); + segment.PackedSize = (uint)(dest.Position - segment.Offset); + xp3entry.Size = segment.PackedSize; + } + } + } + } + } + } + + uint CheckedCopy (Stream src, Stream dst) + { + var checksum = new Adler32(); + var read_buffer = new byte[81920]; + for (;;) + { + int read = src.Read (read_buffer, 0, read_buffer.Length); + if (0 == read) + break; + checksum.Update (read_buffer, 0, read); + dst.Write (read_buffer, 0, read); + } + return checksum.Value; + } + + bool ShouldCompressFile (Entry entry) + { + if ("image" == entry.Type || "archive" == entry.Type) + return false; + if (entry.Name.EndsWith (".ogg", StringComparison.InvariantCultureIgnoreCase)) + return false; + return true; + } + } + + internal class Xp3Stream : Stream + { + ArcView m_file; + Xp3Entry m_entry; + IEnumerator m_segment; + Stream m_stream; + long m_offset = 0; + bool m_eof = false; + + public override bool CanRead { get { return m_stream != null; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return false; } } + public override long Length { get { return m_entry.UnpackedSize; } } + public override long Position + { + get { return m_offset; } + set { throw new NotSupportedException ("Xp3Stream.Position not supported."); } + } + + public Xp3Stream (ArcView file, Xp3Entry entry) + { + m_file = file; + m_entry = entry; + m_segment = entry.Segments.GetEnumerator(); + NextSegment(); + } + + private void NextSegment () + { + if (!m_segment.MoveNext()) + { + m_eof = true; + return; + } + if (null != m_stream) + m_stream.Dispose(); + var segment = m_segment.Current; + m_stream = m_file.CreateStream (segment.Offset, segment.Size); + if (segment.IsCompressed) + m_stream = new ZLibStream (m_stream, CompressionMode.Decompress); + } + + public override int Read (byte[] buffer, int offset, int count) + { + int total = 0; + while (!m_eof && count > 0) + { + int read = m_stream.Read (buffer, offset, count); + m_entry.Cipher.Decrypt (m_entry, m_offset, buffer, offset, read); + m_offset += read; + total += read; + offset += read; + count -= read; + if (0 != count) + NextSegment(); + } + return total; + } + + public override int ReadByte () + { + int b = -1; + while (!m_eof) + { + b = m_stream.ReadByte(); + if (-1 != b) + { + b = m_entry.Cipher.Decrypt (m_entry, m_offset++, (byte)b); + break; + } + NextSegment(); + } + return b; + } + + public override void Flush () + { + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException ("Xp3Stream.Seek method is not supported"); + } + + public override void SetLength (long length) + { + throw new NotSupportedException ("Xp3Stream.SetLength method is not supported"); + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new NotSupportedException ("Xp3Stream.Write method is not supported"); + } + + public override void WriteByte (byte value) + { + throw new NotSupportedException("Xp3Stream.WriteByte method is not supported"); + } + + #region IDisposable Members + bool disposed = false; + protected override void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing && null != m_stream) + { + m_stream.Dispose(); + } + disposed = true; + base.Dispose (disposing); + } + } + #endregion + } + + public abstract class ICrypt + { + /// + /// whether Adler32 checksum should be calculated after contents have been encrypted. + /// + public virtual bool HashAfterCrypt { get { return false; } } + + public virtual byte Decrypt (Xp3Entry entry, long offset, byte value) + { + byte[] buffer = new byte[1] { value }; + Decrypt (entry, offset, buffer, 0, 1); + return buffer[0]; + } + + public abstract void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count); + + public virtual void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + throw new NotImplementedException (arcStrings.MsgEncNotImplemented); + } + } + + public class NoCrypt : ICrypt + { + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + return value; + } + public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + return; + } + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + return; + } + } + + internal class FateCrypt : ICrypt + { + public override bool HashAfterCrypt { get { return true; } } + + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + byte result = (byte)(value ^ 0x36); + if (0x13 == offset) + result ^= 1; + else if (0x2ea29 == offset) + result ^= 3; + return result; + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + for (int i = 0; i < count; ++i) + { + values[pos+i] ^= 0x36; + } + if (offset > 0x2ea29) + return; + if (offset + count > 0x2ea29) + values[pos+0x2ea29-offset] ^= 3; + if (offset > 0x13) + return; + if (offset + count > 0x13) + values[pos+0x13-offset] ^= 1; + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + Decrypt (entry, offset, values, pos, count); + } + } + + internal class HashCrypt : ICrypt + { + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + return (byte)(value ^ entry.Hash); + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + byte key = (byte)entry.Hash; + for (int i = 0; i < count; ++i) + { + values[pos+i] ^= key; + } + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + Decrypt (entry, offset, values, pos, count); + } + } + + internal class XorCrypt : ICrypt + { + private byte m_key; + + public byte Key + { + get { return m_key; } + set { m_key = value; } + } + + public XorCrypt (uint key) + { + m_key = (byte)key; + } + + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + return (byte)(value ^ m_key); + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + for (int i = 0; i < count; ++i) + { + values[pos+i] ^= m_key; + } + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + Decrypt (entry, offset, values, pos, count); + } + } + + internal class SwanSongCrypt : ICrypt + { + static private byte Adjust (uint hash, out int shift) + { + int cl = (int)(hash & 0xff); + if (0 == cl) cl = 0x0f; + shift = cl & 7; + int ch = (int)((hash >> 8) & 0xff); + if (0 == ch) ch = 0xf0; + return (byte)ch; + } + + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + int shift; + byte xor = Adjust (entry.Hash, out shift); + uint data = (uint)(value ^ xor); + return (byte)((data >> shift) | (data << (8 - shift))); + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + int shift; + byte xor = Adjust (entry.Hash, out shift); + for (int i = 0; i < count; ++i) + { + uint data = (uint)(values[pos+i] ^ xor); + values[pos+i] = (byte)((data >> shift) | (data << (8 - shift))); + } + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + int shift; + byte xor = Adjust (entry.Hash, out shift); + for (int i = 0; i < count; ++i) + { + uint data = values[pos+i]; + data = (byte)((data << shift) | (data >> (8 - shift))); + values[pos+i] = (byte)(data ^ xor); + } + } + } + + internal class SeitenCrypt : ICrypt + { + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + uint key = entry.Hash ^ (uint)offset; + if (0 != (key & 2)) + { + int ecx = (int)key & 0x18; + value ^= (byte)((key >> ecx) | (key >> (ecx & 8))); + } + if (0 != (key & 4)) + { + value += (byte)key; + } + if (0 != (key & 8)) + { + value -= (byte)(key >> (int)(key & 0x10)); + } + return value; + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count) + { + for (int i = 0; i < count; ++i) + { + int shift; + uint key = entry.Hash ^ (uint)offset; + byte v = buffer[pos+i]; + if (0 != (key & 2)) + { + shift = (int)key & 0x18; + uint ebx = key >> shift; + shift &= 8; + v ^= (byte)(ebx | (key >> shift)); + } + if (0 != (key & 4)) + { + v += (byte)key; + } + if (0 != (key & 8)) + { + shift = (int)key & 0x10; + v -= (byte)(key >> shift); + } + buffer[pos+i] = v; + ++offset; + } + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + for (int i = 0; i < count; ++i) + { + uint key = entry.Hash ^ (uint)offset; + if (0 != (key & 8)) + { + values[pos+i] += (byte)(key >> (int)(key & 0x10)); + } + if (0 != (key & 4)) + { + values[pos+i] -= (byte)key; + } + if (0 != (key & 2)) + { + int ecx = (int)key & 0x18; + values[pos+i] ^= (byte)((key >> ecx) | (key >> (ecx & 8))); + } + } + } + } + + internal class OkibaCrypt : ICrypt + { + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + if (offset < 0x65) + return (byte)(value ^ (byte)(entry.Hash >> 4)); + uint key = entry.Hash; + // 0,1,2,3 -> 1,0,3,2 + key = ((key & 0xff0000) << 8) | ((key & 0xff000000) >> 8) + | ((key & 0xff00) >> 8) | ((key & 0xff) << 8); + key >>= 8 * ((int)(offset - 0x65) & 3); + return (byte)(value ^ (byte)key); + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + int i = 0; + if (offset < 0x65) + { + uint key = entry.Hash >> 4; + int limit = Math.Min (count, (int)(0x65 - offset)); + for (; i < limit; ++i) + { + values[pos+i] ^= (byte)key; + ++offset; + } + } + if (i < count) + { + offset -= 0x65; + uint key = entry.Hash; + key = ((key & 0xff0000) << 8) | ((key & 0xff000000) >> 8) + | ((key & 0xff00) >> 8) | ((key & 0xff) << 8); + do + { + values[pos+i] ^= (byte)(key >> (8 * ((int)offset & 3))); + ++offset; + } + while (++i < count); + } + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + Decrypt (entry, offset, values, pos, count); + } + } + + internal class SaiminCrypt : ICrypt + { + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + byte key = (byte)entry.Hash; + if (offset < 0x7B) + value ^= (byte)(21 * key); + else if (offset < 0xF6) + value += (byte)(-32 * key); + else if (offset < 0x171) + value ^= (byte)(43 * key); + else if (offset <= 0xffffffffL) + value += (byte)(-54 * key); + return value; + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + byte key = (byte)entry.Hash; + for (int i = 0; i < count && offset <= 0xffffffffL; ++i, ++offset) + { + if (offset < 0x7B) + values[pos+i] ^= (byte)(21 * key); + else if (offset < 0xF6) + values[pos+i] += (byte)(-32 * key); + else if (offset >= 0x171) + values[pos+i] += (byte)(-54 * key); + else + values[pos+i] ^= (byte)(43 * key); + } + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + byte key = (byte)entry.Hash; + for (int i = 0; i < count && offset <= 0xffffffffL; ++i, ++offset) + { + if (offset < 0x7B) + values[pos+i] ^= (byte)(21 * key); + else if (offset < 0xF6) + values[pos+i] -= (byte)(-32 * key); + else if (offset >= 0x171) + values[pos+i] -= (byte)(-54 * key); + else + values[pos+i] ^= (byte)(43 * key); + } + } + } + + internal class DameganeCrypt : ICrypt + { + public override byte Decrypt (Xp3Entry entry, long offset, byte value) + { + if (0 != (offset & 1)) + return (byte)(value ^ entry.Hash); + else + return (byte)(value ^ offset); + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + for (int i = 0; i < count; ++i, ++offset) + { + if (0 != (offset & 1)) + values[pos+i] ^= (byte)entry.Hash; + else + values[pos+i] ^= (byte)offset; + } + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + Decrypt (entry, offset, values, pos, count); + } + } +} diff --git a/ArcFormats/CreateXP3Widget.xaml b/ArcFormats/KiriKiri/CreateXP3Widget.xaml similarity index 100% rename from ArcFormats/CreateXP3Widget.xaml rename to ArcFormats/KiriKiri/CreateXP3Widget.xaml diff --git a/ArcFormats/CreateXP3Widget.xaml.cs b/ArcFormats/KiriKiri/CreateXP3Widget.xaml.cs similarity index 100% rename from ArcFormats/CreateXP3Widget.xaml.cs rename to ArcFormats/KiriKiri/CreateXP3Widget.xaml.cs diff --git a/ArcFormats/ImageTLG.cs b/ArcFormats/KiriKiri/ImageTLG.cs similarity index 97% rename from ArcFormats/ImageTLG.cs rename to ArcFormats/KiriKiri/ImageTLG.cs index 148eb921..c3a1f0dc 100644 --- a/ArcFormats/ImageTLG.cs +++ b/ArcFormats/KiriKiri/ImageTLG.cs @@ -1,1006 +1,1006 @@ -//! \file ImageTLG.cs -//! \date Thu Jul 17 21:31:39 2014 -//! \brief KiriKiri TLG image implementation. -//--------------------------------------------------------------------------- -// TLG5/6 decoder -// Copyright (C) 2000-2005 W.Dee and contributors -// -// C# port by morkt -// - -using System; -using System.IO; -using System.ComponentModel.Composition; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using GameRes.Utility; - -namespace GameRes.Formats.KiriKiri -{ - internal class TlgMetaData : ImageMetaData - { - public int Version; - public int DataOffset; - } - - [Export(typeof(ImageFormat))] - public class TlgFormat : ImageFormat - { - public override string Tag { get { return "TLG"; } } - public override string Description { get { return "KiriKiri game engine image format"; } } - public override uint Signature { get { return 0x30474c54; } } // "TLG0" - - public TlgFormat () - { - Extensions = new string[] { "tlg", "tlg5", "tlg6" }; - Signatures = new uint[] { 0x30474c54, 0x35474c54, 0x36474c54 }; - } - - public override ImageMetaData ReadMetaData (Stream stream) - { - var header = new byte[0x26]; - if (header.Length != stream.Read (header, 0, header.Length)) - return null; - int offset = 0xf; - if (!Binary.AsciiEqual (header, "TLG0.0\x00sds\x1a")) - offset = 0; - bool version6 = Binary.AsciiEqual (header, offset, "TLG6.0\x00raw\x1a"); - if (!version6 && !Binary.AsciiEqual (header, offset, "TLG5.0\x00raw\x1a")) - return null; - int colors = header[offset+11]; - if (version6) - { - if (1 != colors && 4 != colors && 3 != colors) - return null; - if (header[offset+12] != 0 || header[offset+13] != 0 || header[offset+14] != 0) - return null; - offset += 15; - } - else - { - if (4 != colors && 3 != colors) - return null; - offset += 12; - } - uint width = LittleEndian.ToUInt32 (header, offset); - uint height = LittleEndian.ToUInt32 (header, offset+4); - return new TlgMetaData { - Width = width, - Height = height, - BPP = colors*8, - Version = version6 ? 6 : 5, - DataOffset = offset+8, - }; - } - - public override ImageData Read (Stream file, ImageMetaData info) - { - var meta = info as TlgMetaData; - if (null == meta) - throw new System.ArgumentException ("TlgFormat.Read should be supplied with TlgMetaData", "info"); - file.Seek (meta.DataOffset, SeekOrigin.Begin); - if (6 == meta.Version) - return ReadV6 (file, meta); - else - return ReadV5 (file, meta); - } - - public override void Write (Stream file, ImageData image) - { - throw new NotImplementedException ("TlgFormat.Write not implemented"); - } - - const int TVP_TLG6_H_BLOCK_SIZE = 8; - const int TVP_TLG6_W_BLOCK_SIZE = 8; - - const int TVP_TLG6_GOLOMB_N_COUNT = 4; - const int TVP_TLG6_LeadingZeroTable_BITS = 12; - const int TVP_TLG6_LeadingZeroTable_SIZE = (1<> 8 & 0xff); - LZSS_text[p++] = (byte)(i >> 16 & 0xff); - LZSS_text[p++] = (byte)(i >> 24 & 0xff); - LZSS_text[p++] = (byte)(j & 0xff); - LZSS_text[p++] = (byte)(j >> 8 & 0xff); - LZSS_text[p++] = (byte)(j >> 16 & 0xff); - LZSS_text[p++] = (byte)(j >> 24 & 0xff); - } - } - // read chroma filter types. - // chroma filter types are compressed via LZSS as used by TLG5. - { - int inbuf_size = src.ReadInt32(); - byte[] inbuf = src.ReadBytes (inbuf_size); - if (inbuf_size != inbuf.Length) - return null; - TVPTLG5DecompressSlide (filter_types, inbuf, inbuf_size, LZSS_text, 0); - } - - // for each horizontal block group ... - for (int y = 0; y < height; y += TVP_TLG6_H_BLOCK_SIZE) - { - int ylim = y + TVP_TLG6_H_BLOCK_SIZE; - if (ylim >= height) ylim = height; - - int pixel_count = (ylim - y) * width; - - // decode values - for (int c = 0; c < colors; c++) - { - // read bit length - int bit_length = src.ReadInt32(); - - // get compress method - int method = (bit_length >> 30) & 3; - bit_length &= 0x3fffffff; - - // compute byte length - int byte_length = bit_length / 8; - if (0 != (bit_length % 8)) byte_length++; - - // read source from input - src.Read (bit_pool, 0, byte_length); - - // decode values - // two most significant bits of bitlength are - // entropy coding method; - // 00 means Golomb method, - // 01 means Gamma method (not yet suppoted), - // 10 means modified LZSS method (not yet supported), - // 11 means raw (uncompressed) data (not yet supported). - - switch (method) - { - case 0: - if (c == 0 && colors != 1) - TVPTLG6DecodeGolombValuesForFirst (pixelbuf, pixel_count, bit_pool); - else - TVPTLG6DecodeGolombValues (pixelbuf, c*8, pixel_count, bit_pool); - break; - default: - throw new InvalidFormatException ("Unsupported entropy coding method"); - } - } - - // for each line - int ft = (y / TVP_TLG6_H_BLOCK_SIZE) * x_block_count; // within filter_types - int skipbytes = (ylim - y) * TVP_TLG6_W_BLOCK_SIZE; - - for (int yy = y; yy < ylim; yy++) - { - int curline = yy*width; - - int dir = (yy&1)^1; - int oddskip = ((ylim - yy -1) - (yy-y)); - if (0 != main_count) - { - int start = - ((width < TVP_TLG6_W_BLOCK_SIZE) ? width : TVP_TLG6_W_BLOCK_SIZE) * - (yy - y); - TVPTLG6DecodeLineGeneric ( - prevline, prevline_index, - image_bits, curline, - width, 0, main_count, - filter_types, ft, - skipbytes, - pixelbuf, start, - zerocolor, oddskip, dir); - } - - if (main_count != x_block_count) - { - int ww = fraction; - if (ww > TVP_TLG6_W_BLOCK_SIZE) ww = TVP_TLG6_W_BLOCK_SIZE; - int start = ww * (yy - y); - TVPTLG6DecodeLineGeneric ( - prevline, prevline_index, - image_bits, curline, - width, main_count, x_block_count, - filter_types, ft, - skipbytes, - pixelbuf, start, - zerocolor, oddskip, dir); - } - prevline = image_bits; - prevline_index = curline; -// Array.Copy (image_bits, curline, prevline, 0, width); - } - } - unsafe - { - fixed (void* data = image_bits) - { - int stride = width * 4; - PixelFormat format = 32 == info.BPP ? PixelFormats.Bgra32 : PixelFormats.Bgr32; - var bitmap = BitmapSource.Create(width, height, 96, 96, - format, null, (IntPtr) data, height * stride, stride); - bitmap.Freeze(); - return new ImageData(bitmap, info); - } - } - } - } - - ImageData ReadV5 (Stream stream, TlgMetaData info) - { - using (var src = new ArcView.Reader (stream)) - { - int width = (int)info.Width; - int height = (int)info.Height; - int colors = info.BPP / 8; - int blockheight = src.ReadInt32(); - int blockcount = (height - 1) / blockheight + 1; - - // skip block size section - src.BaseStream.Seek (blockcount * 4, SeekOrigin.Current); - - int stride = width * 4; - var image_bits = new byte[height * stride]; - var text = new byte[4096]; - for (int i = 0; i < 4096; ++i) - text[i] = 0; - - var inbuf = new byte[blockheight * width + 10]; - byte [][] outbuf = new byte[4][]; - for (int i = 0; i < colors; i++) - outbuf[i] = new byte[blockheight * width + 10]; - - int z = 0; - int prevline = -1; - for (int y_blk = 0; y_blk < height; y_blk += blockheight) - { - // read file and decompress - for (int c = 0; c < colors; c++) - { - byte mark = src.ReadByte(); - int size; - size = src.ReadInt32(); - if (mark == 0) - { - // modified LZSS compressed data - if (size != src.Read (inbuf, 0, size)) - return null; - z = TVPTLG5DecompressSlide (outbuf[c], inbuf, size, text, z); - } - else - { - // raw data - src.Read (outbuf[c], 0, size); - } - } - - // compose colors and store - int y_lim = y_blk + blockheight; - if (y_lim > height) y_lim = height; - int outbuf_pos = 0; - for (int y = y_blk; y < y_lim; y++) - { - int current = y * stride; - int current_org = current; - if (prevline >= 0) - { - // not first line - switch(colors) - { - case 3: - TVPTLG5ComposeColors3To4 (image_bits, current, prevline, - outbuf, outbuf_pos, width); - break; - case 4: - TVPTLG5ComposeColors4To4 (image_bits, current, prevline, - outbuf, outbuf_pos, width); - break; - } - } - else - { - // first line - switch(colors) - { - case 3: - for (int pr = 0, pg = 0, pb = 0, x = 0; - x < width; x++) - { - int b = outbuf[0][outbuf_pos+x]; - int g = outbuf[1][outbuf_pos+x]; - int r = outbuf[2][outbuf_pos+x]; - b += g; r += g; - image_bits[current++] = (byte)(pb += b); - image_bits[current++] = (byte)(pg += g); - image_bits[current++] = (byte)(pr += r); - image_bits[current++] = 0xff; - } - break; - case 4: - for (int pr = 0, pg = 0, pb = 0, pa = 0, x = 0; - x < width; x++) - { - int b = outbuf[0][outbuf_pos+x]; - int g = outbuf[1][outbuf_pos+x]; - int r = outbuf[2][outbuf_pos+x]; - int a = outbuf[3][outbuf_pos+x]; - b += g; r += g; - image_bits[current++] = (byte)(pb += b); - image_bits[current++] = (byte)(pg += g); - image_bits[current++] = (byte)(pr += r); - image_bits[current++] = (byte)(pa += a); - } - break; - } - } - outbuf_pos += width; - prevline = current_org; - } - } - PixelFormat format = 4 == colors ? PixelFormats.Bgra32 : PixelFormats.Bgr32; - var bitmap = BitmapSource.Create (width, height, 96, 96, - format, null, image_bits, stride); - bitmap.Freeze(); - return new ImageData (bitmap, info); - } - } - - void TVPTLG5ComposeColors3To4 (byte[] outp, int outp_index, int upper, - byte[][] buf, int bufpos, int width) - { - byte pc0 = 0, pc1 = 0, pc2 = 0; - byte c0, c1, c2; - for (int x = 0; x < width; x++) - { - c0 = buf[0][bufpos+x]; - c1 = buf[1][bufpos+x]; - c2 = buf[2][bufpos+x]; - c0 += c1; c2 += c1; - outp[outp_index++] = (byte)(((pc0 += c0) + outp[upper+0]) & 0xff); - outp[outp_index++] = (byte)(((pc1 += c1) + outp[upper+1]) & 0xff); - outp[outp_index++] = (byte)(((pc2 += c2) + outp[upper+2]) & 0xff); - outp[outp_index++] = 0xff; - upper += 4; - } - } - - void TVPTLG5ComposeColors4To4 (byte[] outp, int outp_index, int upper, - byte[][] buf, int bufpos, int width) - { - byte pc0 = 0, pc1 = 0, pc2 = 0, pc3 = 0; - byte c0, c1, c2, c3; - for (int x = 0; x < width; x++) - { - c0 = buf[0][bufpos+x]; - c1 = buf[1][bufpos+x]; - c2 = buf[2][bufpos+x]; - c3 = buf[3][bufpos+x]; - c0 += c1; c2 += c1; - outp[outp_index++] = (byte)(((pc0 += c0) + outp[upper+0]) & 0xff); - outp[outp_index++] = (byte)(((pc1 += c1) + outp[upper+1]) & 0xff); - outp[outp_index++] = (byte)(((pc2 += c2) + outp[upper+2]) & 0xff); - outp[outp_index++] = (byte)(((pc3 += c3) + outp[upper+3]) & 0xff); - upper += 4; - } - } - - int TVPTLG5DecompressSlide (byte[] outbuf, byte[] inbuf, int inbuf_size, byte[] text, int initialr) - { - int r = initialr; - uint flags = 0; - int o = 0; - for (int i = 0; i < inbuf_size; ) - { - if (((flags >>= 1) & 256) == 0) - { - flags = (uint)(inbuf[i++] | 0xff00); - } - if (0 != (flags & 1)) - { - int mpos = inbuf[i] | ((inbuf[i+1] & 0xf) << 8); - int mlen = (inbuf[i+1] & 0xf0) >> 4; - i += 2; - mlen += 3; - if (mlen == 18) mlen += inbuf[i++]; - - while (0 != mlen--) - { - outbuf[o++] = text[r++] = text[mpos++]; - mpos &= (4096 - 1); - r &= (4096 - 1); - } - } - else - { - byte c = inbuf[i++]; - outbuf[o++] = c; - text[r++] = c; - r &= (4096 - 1); - } - } - return r; - } - - static uint tvp_make_gt_mask (uint a, uint b) - { - uint tmp2 = ~b; - uint tmp = ((a & tmp2) + (((a ^ tmp2) >> 1) & 0x7f7f7f7f) ) & 0x80808080; - tmp = ((tmp >> 7) + 0x7f7f7f7f) ^ 0x7f7f7f7f; - return tmp; - } - - static uint tvp_packed_bytes_add (uint a, uint b) - { - uint tmp = (uint)((((a & b)<<1) + ((a ^ b) & 0xfefefefe) ) & 0x01010100); - return a+b-tmp; - } - - static uint tvp_med2 (uint a, uint b, uint c) - { - /* do Median Edge Detector thx, Mr. sugi at kirikiri.info */ - uint aa_gt_bb = tvp_make_gt_mask(a, b); - uint a_xor_b_and_aa_gt_bb = ((a ^ b) & aa_gt_bb); - uint aa = a_xor_b_and_aa_gt_bb ^ a; - uint bb = a_xor_b_and_aa_gt_bb ^ b; - uint n = tvp_make_gt_mask(c, bb); - uint nn = tvp_make_gt_mask(aa, c); - uint m = ~(n | nn); - return (n & aa) | (nn & bb) | ((bb & m) - (c & m) + (aa & m)); - } - - static uint tvp_med (uint a, uint b, uint c, uint v) - { - return tvp_packed_bytes_add (tvp_med2 (a, b, c), v); - } - - static uint tvp_avg (uint a, uint b, uint c, uint v) - { - return tvp_packed_bytes_add ((((a&b) + (((a^b) & 0xfefefefe) >> 1)) + ((a^b)&0x01010101)), v); - } - - /* -#define TVP_TLG6_DO_CHROMA_DECODE(N, R, G, B) case (N<<1): \ - TVP_TLG6_DO_CHROMA_DECODE_PROTO(R, G, B, IA, {inbuf_index+=step;}) break; \ - case (N<<1)+1: \ - TVP_TLG6_DO_CHROMA_DECODE_PROTO2(R, G, B, IA, {inbuf_index+=step;}) break; - -#define TVP_TLG6_DO_CHROMA_DECODE_PROTO(B, G, R, A, POST_INCREMENT) do \ - { \ - uint u = prevline[prevline_index]; \ - p = tvp_med(p, u, up, \ - (0xff0000 & ((R)<<16)) + (0xff00 & ((G)<<8)) + (0xff & (B)) + ((A) << 24) ); \ - up = u; \ - curline[curline_index] = p; \ - curline_index++; \ - prevline_index++; \ - POST_INCREMENT \ - } while(--w); -#define TVP_TLG6_DO_CHROMA_DECODE_PROTO2(B, G, R, A, POST_INCREMENT) do \ - { \ - uint u = *prevline; \ - p = avg(p, u, up, \ - (0xff0000 & ((R)<<16)) + (0xff00 & ((G)<<8)) + (0xff & (B)) + ((A) << 24) ); \ - up = u; \ - *curline = p; \ - curline ++; \ - prevline ++; \ - POST_INCREMENT \ - } while(--w); -*/ - delegate uint tvp_decoder (uint a, uint b, uint c, uint v); - - void TVPTLG6DecodeLineGeneric (uint[] prevline, int prevline_index, - uint[] curline, int curline_index, - int width, int start_block, int block_limit, - byte[] filtertypes, int filtertypes_index, - int skipblockbytes, - uint[] inbuf, int inbuf_index, - uint initialp, int oddskip, int dir) - { - /* - chroma/luminosity decoding - (this does reordering, color correlation filter, MED/AVG at a time) - */ - uint p, up; - - if (0 != start_block) - { - prevline_index += start_block * TVP_TLG6_W_BLOCK_SIZE; - curline_index += start_block * TVP_TLG6_W_BLOCK_SIZE; - p = curline[curline_index-1]; - up = prevline[prevline_index-1]; - } - else - { - p = up = initialp; - } - - inbuf_index += skipblockbytes * start_block; - int step = 0 != (dir & 1) ? 1 : -1; - - for (int i = start_block; i < block_limit; i++) - { - int w = width - i*TVP_TLG6_W_BLOCK_SIZE; - if (w > TVP_TLG6_W_BLOCK_SIZE) w = TVP_TLG6_W_BLOCK_SIZE; - int ww = w; - if (step == -1) inbuf_index += ww-1; - if (0 != (i & 1)) inbuf_index += oddskip * ww; - -// byte IA = (byte)(inbuf[inbuf_index]>>24); -// byte IR = (byte)(inbuf[inbuf_index]>>16); -// byte IG = (byte)(inbuf[inbuf_index]>>8 ); -// byte IB = (byte)(inbuf[inbuf_index] ); - tvp_decoder decoder; - switch (filtertypes[filtertypes_index+i]) - { -// TVP_TLG6_DO_CHROMA_DECODE( 0, IB, IG, IR); - case 0: - decoder = (a, b, c, v) => tvp_med (a, b, c, v); - break; - case 1: - decoder = (a, b, c, v) => tvp_avg (a, b, c, v); - break; -// TVP_TLG6_DO_CHROMA_DECODE( 1, IB+IG, IG, IR+IG); - case 2: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (((v>>8)&0xff)<<8) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); - break; - case 3: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (((v>>8)&0xff)<<8) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); - break; -// TVP_TLG6_DO_CHROMA_DECODE( 2, IB, IG+IB, IR+IB+IG); - case 4: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 5: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; -// TVP_TLG6_DO_CHROMA_DECODE( 3, IB+IR+IG, IG+IR, IR); - case 6: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); - break; - case 7: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); - break; - case 8: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); - break; - case 9: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); - break; - case 10: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); - break; - case 11: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); - break; - case 12: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); - break; - case 13: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); - break; - case 14: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 15: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 16: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 17: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 18: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))) + ((v&0xff000000)))); - break; - case 19: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))) + ((v&0xff000000)))); - break; - case 20: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); - break; - case 21: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); - break; - case 22: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 23: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 24: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 25: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 26: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); - break; - case 27: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); - break; - case 28: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); - break; - case 29: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); - break; -// TVP_TLG6_DO_CHROMA_DECODE(15, IB, IG+(IB<<1), IR+(IB<<1)); - case 30: - decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+((v&0xff)<<1))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v&0xff)<<1))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - case 31: - decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) - ((0xff0000 & ((((v>>16)&0xff)+((v&0xff)<<1))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v&0xff)<<1))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); - break; - default: return; - } - do { - uint u = prevline[prevline_index]; - p = decoder (p, u, up, inbuf[inbuf_index]); - up = u; - curline[curline_index] = p; - curline_index++; - prevline_index++; - inbuf_index += step; - } while (0 != --w); - if (step == 1) - inbuf_index += skipblockbytes - ww; - else - inbuf_index += skipblockbytes + 1; - if (0 != (i&1)) inbuf_index -= oddskip * ww; - } - } - - static class TVP_Tables - { - public static byte[] TVPTLG6LeadingZeroTable = new byte[TVP_TLG6_LeadingZeroTable_SIZE]; - public static sbyte[,] TVPTLG6GolombBitLengthTable = new sbyte - [TVP_TLG6_GOLOMB_N_COUNT*2*128, TVP_TLG6_GOLOMB_N_COUNT]; - static short[,] TVPTLG6GolombCompressed = new short[TVP_TLG6_GOLOMB_N_COUNT,9] { - {3,7,15,27,63,108,223,448,130,}, - {3,5,13,24,51,95,192,384,257,}, - {2,5,12,21,39,86,155,320,384,}, - {2,3,9,18,33,61,129,258,511,}, - /* Tuned by W.Dee, 2004/03/25 */ - }; - - static TVP_Tables () - { -// TVPInitDitherTable(); - TVPTLG6InitLeadingZeroTable(); - TVPTLG6InitGolombTable(); - } - - static void TVPTLG6InitLeadingZeroTable () - { - /* table which indicates first set bit position + 1. */ - /* this may be replaced by BSF (IA32 instrcution). */ - - for (int i = 0; i < TVP_TLG6_LeadingZeroTable_SIZE; i++) - { - int cnt = 0; - int j; - for(j = 1; j != TVP_TLG6_LeadingZeroTable_SIZE && 0 == (i & j); - j <<= 1, cnt++); - cnt++; - if (j == TVP_TLG6_LeadingZeroTable_SIZE) cnt = 0; - TVPTLG6LeadingZeroTable[i] = (byte)cnt; - } - } - - static void TVPTLG6InitGolombTable() - { - for (int n = 0; n < TVP_TLG6_GOLOMB_N_COUNT; n++) - { - int a = 0; - for (int i = 0; i < 9; i++) - { - for (int j = 0; j < TVPTLG6GolombCompressed[n,i]; j++) - TVPTLG6GolombBitLengthTable[a++,n] = (sbyte)i; - } - if(a != TVP_TLG6_GOLOMB_N_COUNT*2*128) - throw new Exception ("Invalid data initialization"); /* THIS MUST NOT BE EXECUETED! */ - /* (this is for compressed table data check) */ - } - } - } - - void TVPTLG6DecodeGolombValuesForFirst (uint[] pixelbuf, int pixel_count, byte[] bit_pool) - { - /* - decode values packed in "bit_pool". - values are coded using golomb code. - - "ForFirst" function do dword access to pixelbuf, - clearing with zero except for blue (least siginificant byte). - */ - int bit_pool_index = 0; - - int n = TVP_TLG6_GOLOMB_N_COUNT - 1; /* output counter */ - int a = 0; /* summary of absolute values of errors */ - - int bit_pos = 1; - bool zero = 0 == (bit_pool[bit_pool_index] & 1); - - for (int pixel = 0; pixel < pixel_count; ) - { - /* get running count */ - int count; - - { - uint t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; - int b = TVP_Tables.TVPTLG6LeadingZeroTable[t & (TVP_TLG6_LeadingZeroTable_SIZE-1)]; - int bit_count = b; - while (0 == b) - { - bit_count += TVP_TLG6_LeadingZeroTable_BITS; - bit_pos += TVP_TLG6_LeadingZeroTable_BITS; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; - b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; - bit_count += b; - } - bit_pos += b; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - - bit_count --; - count = 1 << bit_count; - count += ((LittleEndian.ToInt32 (bit_pool, bit_pool_index) >> (bit_pos)) & (count-1)); - - bit_pos += bit_count; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - } - if (zero) - { - /* zero values */ - - /* fill distination with zero */ - do { pixelbuf[pixel++] = 0; } while (0 != --count); - - zero = !zero; - } - else - { - /* non-zero values */ - - /* fill distination with glomb code */ - - do - { - int k = TVP_Tables.TVPTLG6GolombBitLengthTable[a,n]; - int v, sign; - - uint t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; - int bit_count; - int b; - if (0 != t) - { - b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; - bit_count = b; - while (0 == b) - { - bit_count += TVP_TLG6_LeadingZeroTable_BITS; - bit_pos += TVP_TLG6_LeadingZeroTable_BITS; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; - b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; - bit_count += b; - } - bit_count --; - } - else - { - bit_pool_index += 5; - bit_count = bit_pool[bit_pool_index-1]; - bit_pos = 0; - t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index); - b = 0; - } - - v = (int)((bit_count << k) + ((t >> b) & ((1<>= 1; - a += v; - pixelbuf[pixel++] = (byte)((v ^ sign) + sign + 1); - - bit_pos += b; - bit_pos += k; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - - if (--n < 0) - { - a >>= 1; - n = TVP_TLG6_GOLOMB_N_COUNT - 1; - } - } while (0 != --count); - zero = !zero; - } - } - } - - void TVPTLG6DecodeGolombValues (uint[] pixelbuf, int offset, int pixel_count, byte[] bit_pool) - { - /* - decode values packed in "bit_pool". - values are coded using golomb code. - */ - uint mask = (uint)~(0xff << offset); - int bit_pool_index = 0; - - int n = TVP_TLG6_GOLOMB_N_COUNT - 1; /* output counter */ - int a = 0; /* summary of absolute values of errors */ - - int bit_pos = 1; - bool zero = 0 == (bit_pool[bit_pool_index] & 1); - - for (int pixel = 0; pixel < pixel_count; ) - { - /* get running count */ - int count; - - { - uint t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; - int b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; - int bit_count = b; - while (0 == b) - { - bit_count += TVP_TLG6_LeadingZeroTable_BITS; - bit_pos += TVP_TLG6_LeadingZeroTable_BITS; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; - b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; - bit_count += b; - } - bit_pos += b; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - - bit_count --; - count = 1 << bit_count; - count += (int)((LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> (bit_pos)) & (count-1)); - - bit_pos += bit_count; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - } - if (zero) - { - /* zero values */ - - /* fill distination with zero */ - do { pixelbuf[pixel++] &= mask; } while (0 != --count); - - zero = !zero; - } - else - { - /* non-zero values */ - - /* fill distination with glomb code */ - - do - { - int k = TVP_Tables.TVPTLG6GolombBitLengthTable[a,n]; - int v, sign; - - uint t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; - int bit_count; - int b; - if (0 != t) - { - b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; - bit_count = b; - while (0 == b) - { - bit_count += TVP_TLG6_LeadingZeroTable_BITS; - bit_pos += TVP_TLG6_LeadingZeroTable_BITS; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; - b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; - bit_count += b; - } - bit_count --; - } - else - { - bit_pool_index += 5; - bit_count = bit_pool[bit_pool_index-1]; - bit_pos = 0; - t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index); - b = 0; - } - - v = (int)((bit_count << k) + ((t >> b) & ((1<>= 1; - a += v; - uint c = (uint)((pixelbuf[pixel] & mask) | (uint)((byte)((v ^ sign) + sign + 1) << offset)); - pixelbuf[pixel++] = c; - - bit_pos += b; - bit_pos += k; - bit_pool_index += bit_pos >> 3; - bit_pos &= 7; - - if (--n < 0) - { - a >>= 1; - n = TVP_TLG6_GOLOMB_N_COUNT - 1; - } - } while (0 != --count); - zero = !zero; - } - } - } - } -} +//! \file ImageTLG.cs +//! \date Thu Jul 17 21:31:39 2014 +//! \brief KiriKiri TLG image implementation. +//--------------------------------------------------------------------------- +// TLG5/6 decoder +// Copyright (C) 2000-2005 W.Dee and contributors +// +// C# port by morkt +// + +using System; +using System.IO; +using System.ComponentModel.Composition; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.KiriKiri +{ + internal class TlgMetaData : ImageMetaData + { + public int Version; + public int DataOffset; + } + + [Export(typeof(ImageFormat))] + public class TlgFormat : ImageFormat + { + public override string Tag { get { return "TLG"; } } + public override string Description { get { return "KiriKiri game engine image format"; } } + public override uint Signature { get { return 0x30474c54; } } // "TLG0" + + public TlgFormat () + { + Extensions = new string[] { "tlg", "tlg5", "tlg6" }; + Signatures = new uint[] { 0x30474c54, 0x35474c54, 0x36474c54 }; + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x26]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + int offset = 0xf; + if (!Binary.AsciiEqual (header, "TLG0.0\x00sds\x1a")) + offset = 0; + bool version6 = Binary.AsciiEqual (header, offset, "TLG6.0\x00raw\x1a"); + if (!version6 && !Binary.AsciiEqual (header, offset, "TLG5.0\x00raw\x1a")) + return null; + int colors = header[offset+11]; + if (version6) + { + if (1 != colors && 4 != colors && 3 != colors) + return null; + if (header[offset+12] != 0 || header[offset+13] != 0 || header[offset+14] != 0) + return null; + offset += 15; + } + else + { + if (4 != colors && 3 != colors) + return null; + offset += 12; + } + uint width = LittleEndian.ToUInt32 (header, offset); + uint height = LittleEndian.ToUInt32 (header, offset+4); + return new TlgMetaData { + Width = width, + Height = height, + BPP = colors*8, + Version = version6 ? 6 : 5, + DataOffset = offset+8, + }; + } + + public override ImageData Read (Stream file, ImageMetaData info) + { + var meta = info as TlgMetaData; + if (null == meta) + throw new System.ArgumentException ("TlgFormat.Read should be supplied with TlgMetaData", "info"); + file.Seek (meta.DataOffset, SeekOrigin.Begin); + if (6 == meta.Version) + return ReadV6 (file, meta); + else + return ReadV5 (file, meta); + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("TlgFormat.Write not implemented"); + } + + const int TVP_TLG6_H_BLOCK_SIZE = 8; + const int TVP_TLG6_W_BLOCK_SIZE = 8; + + const int TVP_TLG6_GOLOMB_N_COUNT = 4; + const int TVP_TLG6_LeadingZeroTable_BITS = 12; + const int TVP_TLG6_LeadingZeroTable_SIZE = (1<> 8 & 0xff); + LZSS_text[p++] = (byte)(i >> 16 & 0xff); + LZSS_text[p++] = (byte)(i >> 24 & 0xff); + LZSS_text[p++] = (byte)(j & 0xff); + LZSS_text[p++] = (byte)(j >> 8 & 0xff); + LZSS_text[p++] = (byte)(j >> 16 & 0xff); + LZSS_text[p++] = (byte)(j >> 24 & 0xff); + } + } + // read chroma filter types. + // chroma filter types are compressed via LZSS as used by TLG5. + { + int inbuf_size = src.ReadInt32(); + byte[] inbuf = src.ReadBytes (inbuf_size); + if (inbuf_size != inbuf.Length) + return null; + TVPTLG5DecompressSlide (filter_types, inbuf, inbuf_size, LZSS_text, 0); + } + + // for each horizontal block group ... + for (int y = 0; y < height; y += TVP_TLG6_H_BLOCK_SIZE) + { + int ylim = y + TVP_TLG6_H_BLOCK_SIZE; + if (ylim >= height) ylim = height; + + int pixel_count = (ylim - y) * width; + + // decode values + for (int c = 0; c < colors; c++) + { + // read bit length + int bit_length = src.ReadInt32(); + + // get compress method + int method = (bit_length >> 30) & 3; + bit_length &= 0x3fffffff; + + // compute byte length + int byte_length = bit_length / 8; + if (0 != (bit_length % 8)) byte_length++; + + // read source from input + src.Read (bit_pool, 0, byte_length); + + // decode values + // two most significant bits of bitlength are + // entropy coding method; + // 00 means Golomb method, + // 01 means Gamma method (not yet suppoted), + // 10 means modified LZSS method (not yet supported), + // 11 means raw (uncompressed) data (not yet supported). + + switch (method) + { + case 0: + if (c == 0 && colors != 1) + TVPTLG6DecodeGolombValuesForFirst (pixelbuf, pixel_count, bit_pool); + else + TVPTLG6DecodeGolombValues (pixelbuf, c*8, pixel_count, bit_pool); + break; + default: + throw new InvalidFormatException ("Unsupported entropy coding method"); + } + } + + // for each line + int ft = (y / TVP_TLG6_H_BLOCK_SIZE) * x_block_count; // within filter_types + int skipbytes = (ylim - y) * TVP_TLG6_W_BLOCK_SIZE; + + for (int yy = y; yy < ylim; yy++) + { + int curline = yy*width; + + int dir = (yy&1)^1; + int oddskip = ((ylim - yy -1) - (yy-y)); + if (0 != main_count) + { + int start = + ((width < TVP_TLG6_W_BLOCK_SIZE) ? width : TVP_TLG6_W_BLOCK_SIZE) * + (yy - y); + TVPTLG6DecodeLineGeneric ( + prevline, prevline_index, + image_bits, curline, + width, 0, main_count, + filter_types, ft, + skipbytes, + pixelbuf, start, + zerocolor, oddskip, dir); + } + + if (main_count != x_block_count) + { + int ww = fraction; + if (ww > TVP_TLG6_W_BLOCK_SIZE) ww = TVP_TLG6_W_BLOCK_SIZE; + int start = ww * (yy - y); + TVPTLG6DecodeLineGeneric ( + prevline, prevline_index, + image_bits, curline, + width, main_count, x_block_count, + filter_types, ft, + skipbytes, + pixelbuf, start, + zerocolor, oddskip, dir); + } + prevline = image_bits; + prevline_index = curline; +// Array.Copy (image_bits, curline, prevline, 0, width); + } + } + unsafe + { + fixed (void* data = image_bits) + { + int stride = width * 4; + PixelFormat format = 32 == info.BPP ? PixelFormats.Bgra32 : PixelFormats.Bgr32; + var bitmap = BitmapSource.Create(width, height, 96, 96, + format, null, (IntPtr) data, height * stride, stride); + bitmap.Freeze(); + return new ImageData(bitmap, info); + } + } + } + } + + ImageData ReadV5 (Stream stream, TlgMetaData info) + { + using (var src = new ArcView.Reader (stream)) + { + int width = (int)info.Width; + int height = (int)info.Height; + int colors = info.BPP / 8; + int blockheight = src.ReadInt32(); + int blockcount = (height - 1) / blockheight + 1; + + // skip block size section + src.BaseStream.Seek (blockcount * 4, SeekOrigin.Current); + + int stride = width * 4; + var image_bits = new byte[height * stride]; + var text = new byte[4096]; + for (int i = 0; i < 4096; ++i) + text[i] = 0; + + var inbuf = new byte[blockheight * width + 10]; + byte [][] outbuf = new byte[4][]; + for (int i = 0; i < colors; i++) + outbuf[i] = new byte[blockheight * width + 10]; + + int z = 0; + int prevline = -1; + for (int y_blk = 0; y_blk < height; y_blk += blockheight) + { + // read file and decompress + for (int c = 0; c < colors; c++) + { + byte mark = src.ReadByte(); + int size; + size = src.ReadInt32(); + if (mark == 0) + { + // modified LZSS compressed data + if (size != src.Read (inbuf, 0, size)) + return null; + z = TVPTLG5DecompressSlide (outbuf[c], inbuf, size, text, z); + } + else + { + // raw data + src.Read (outbuf[c], 0, size); + } + } + + // compose colors and store + int y_lim = y_blk + blockheight; + if (y_lim > height) y_lim = height; + int outbuf_pos = 0; + for (int y = y_blk; y < y_lim; y++) + { + int current = y * stride; + int current_org = current; + if (prevline >= 0) + { + // not first line + switch(colors) + { + case 3: + TVPTLG5ComposeColors3To4 (image_bits, current, prevline, + outbuf, outbuf_pos, width); + break; + case 4: + TVPTLG5ComposeColors4To4 (image_bits, current, prevline, + outbuf, outbuf_pos, width); + break; + } + } + else + { + // first line + switch(colors) + { + case 3: + for (int pr = 0, pg = 0, pb = 0, x = 0; + x < width; x++) + { + int b = outbuf[0][outbuf_pos+x]; + int g = outbuf[1][outbuf_pos+x]; + int r = outbuf[2][outbuf_pos+x]; + b += g; r += g; + image_bits[current++] = (byte)(pb += b); + image_bits[current++] = (byte)(pg += g); + image_bits[current++] = (byte)(pr += r); + image_bits[current++] = 0xff; + } + break; + case 4: + for (int pr = 0, pg = 0, pb = 0, pa = 0, x = 0; + x < width; x++) + { + int b = outbuf[0][outbuf_pos+x]; + int g = outbuf[1][outbuf_pos+x]; + int r = outbuf[2][outbuf_pos+x]; + int a = outbuf[3][outbuf_pos+x]; + b += g; r += g; + image_bits[current++] = (byte)(pb += b); + image_bits[current++] = (byte)(pg += g); + image_bits[current++] = (byte)(pr += r); + image_bits[current++] = (byte)(pa += a); + } + break; + } + } + outbuf_pos += width; + prevline = current_org; + } + } + PixelFormat format = 4 == colors ? PixelFormats.Bgra32 : PixelFormats.Bgr32; + var bitmap = BitmapSource.Create (width, height, 96, 96, + format, null, image_bits, stride); + bitmap.Freeze(); + return new ImageData (bitmap, info); + } + } + + void TVPTLG5ComposeColors3To4 (byte[] outp, int outp_index, int upper, + byte[][] buf, int bufpos, int width) + { + byte pc0 = 0, pc1 = 0, pc2 = 0; + byte c0, c1, c2; + for (int x = 0; x < width; x++) + { + c0 = buf[0][bufpos+x]; + c1 = buf[1][bufpos+x]; + c2 = buf[2][bufpos+x]; + c0 += c1; c2 += c1; + outp[outp_index++] = (byte)(((pc0 += c0) + outp[upper+0]) & 0xff); + outp[outp_index++] = (byte)(((pc1 += c1) + outp[upper+1]) & 0xff); + outp[outp_index++] = (byte)(((pc2 += c2) + outp[upper+2]) & 0xff); + outp[outp_index++] = 0xff; + upper += 4; + } + } + + void TVPTLG5ComposeColors4To4 (byte[] outp, int outp_index, int upper, + byte[][] buf, int bufpos, int width) + { + byte pc0 = 0, pc1 = 0, pc2 = 0, pc3 = 0; + byte c0, c1, c2, c3; + for (int x = 0; x < width; x++) + { + c0 = buf[0][bufpos+x]; + c1 = buf[1][bufpos+x]; + c2 = buf[2][bufpos+x]; + c3 = buf[3][bufpos+x]; + c0 += c1; c2 += c1; + outp[outp_index++] = (byte)(((pc0 += c0) + outp[upper+0]) & 0xff); + outp[outp_index++] = (byte)(((pc1 += c1) + outp[upper+1]) & 0xff); + outp[outp_index++] = (byte)(((pc2 += c2) + outp[upper+2]) & 0xff); + outp[outp_index++] = (byte)(((pc3 += c3) + outp[upper+3]) & 0xff); + upper += 4; + } + } + + int TVPTLG5DecompressSlide (byte[] outbuf, byte[] inbuf, int inbuf_size, byte[] text, int initialr) + { + int r = initialr; + uint flags = 0; + int o = 0; + for (int i = 0; i < inbuf_size; ) + { + if (((flags >>= 1) & 256) == 0) + { + flags = (uint)(inbuf[i++] | 0xff00); + } + if (0 != (flags & 1)) + { + int mpos = inbuf[i] | ((inbuf[i+1] & 0xf) << 8); + int mlen = (inbuf[i+1] & 0xf0) >> 4; + i += 2; + mlen += 3; + if (mlen == 18) mlen += inbuf[i++]; + + while (0 != mlen--) + { + outbuf[o++] = text[r++] = text[mpos++]; + mpos &= (4096 - 1); + r &= (4096 - 1); + } + } + else + { + byte c = inbuf[i++]; + outbuf[o++] = c; + text[r++] = c; + r &= (4096 - 1); + } + } + return r; + } + + static uint tvp_make_gt_mask (uint a, uint b) + { + uint tmp2 = ~b; + uint tmp = ((a & tmp2) + (((a ^ tmp2) >> 1) & 0x7f7f7f7f) ) & 0x80808080; + tmp = ((tmp >> 7) + 0x7f7f7f7f) ^ 0x7f7f7f7f; + return tmp; + } + + static uint tvp_packed_bytes_add (uint a, uint b) + { + uint tmp = (uint)((((a & b)<<1) + ((a ^ b) & 0xfefefefe) ) & 0x01010100); + return a+b-tmp; + } + + static uint tvp_med2 (uint a, uint b, uint c) + { + /* do Median Edge Detector thx, Mr. sugi at kirikiri.info */ + uint aa_gt_bb = tvp_make_gt_mask(a, b); + uint a_xor_b_and_aa_gt_bb = ((a ^ b) & aa_gt_bb); + uint aa = a_xor_b_and_aa_gt_bb ^ a; + uint bb = a_xor_b_and_aa_gt_bb ^ b; + uint n = tvp_make_gt_mask(c, bb); + uint nn = tvp_make_gt_mask(aa, c); + uint m = ~(n | nn); + return (n & aa) | (nn & bb) | ((bb & m) - (c & m) + (aa & m)); + } + + static uint tvp_med (uint a, uint b, uint c, uint v) + { + return tvp_packed_bytes_add (tvp_med2 (a, b, c), v); + } + + static uint tvp_avg (uint a, uint b, uint c, uint v) + { + return tvp_packed_bytes_add ((((a&b) + (((a^b) & 0xfefefefe) >> 1)) + ((a^b)&0x01010101)), v); + } + + /* +#define TVP_TLG6_DO_CHROMA_DECODE(N, R, G, B) case (N<<1): \ + TVP_TLG6_DO_CHROMA_DECODE_PROTO(R, G, B, IA, {inbuf_index+=step;}) break; \ + case (N<<1)+1: \ + TVP_TLG6_DO_CHROMA_DECODE_PROTO2(R, G, B, IA, {inbuf_index+=step;}) break; + +#define TVP_TLG6_DO_CHROMA_DECODE_PROTO(B, G, R, A, POST_INCREMENT) do \ + { \ + uint u = prevline[prevline_index]; \ + p = tvp_med(p, u, up, \ + (0xff0000 & ((R)<<16)) + (0xff00 & ((G)<<8)) + (0xff & (B)) + ((A) << 24) ); \ + up = u; \ + curline[curline_index] = p; \ + curline_index++; \ + prevline_index++; \ + POST_INCREMENT \ + } while(--w); +#define TVP_TLG6_DO_CHROMA_DECODE_PROTO2(B, G, R, A, POST_INCREMENT) do \ + { \ + uint u = *prevline; \ + p = avg(p, u, up, \ + (0xff0000 & ((R)<<16)) + (0xff00 & ((G)<<8)) + (0xff & (B)) + ((A) << 24) ); \ + up = u; \ + *curline = p; \ + curline ++; \ + prevline ++; \ + POST_INCREMENT \ + } while(--w); +*/ + delegate uint tvp_decoder (uint a, uint b, uint c, uint v); + + void TVPTLG6DecodeLineGeneric (uint[] prevline, int prevline_index, + uint[] curline, int curline_index, + int width, int start_block, int block_limit, + byte[] filtertypes, int filtertypes_index, + int skipblockbytes, + uint[] inbuf, int inbuf_index, + uint initialp, int oddskip, int dir) + { + /* + chroma/luminosity decoding + (this does reordering, color correlation filter, MED/AVG at a time) + */ + uint p, up; + + if (0 != start_block) + { + prevline_index += start_block * TVP_TLG6_W_BLOCK_SIZE; + curline_index += start_block * TVP_TLG6_W_BLOCK_SIZE; + p = curline[curline_index-1]; + up = prevline[prevline_index-1]; + } + else + { + p = up = initialp; + } + + inbuf_index += skipblockbytes * start_block; + int step = 0 != (dir & 1) ? 1 : -1; + + for (int i = start_block; i < block_limit; i++) + { + int w = width - i*TVP_TLG6_W_BLOCK_SIZE; + if (w > TVP_TLG6_W_BLOCK_SIZE) w = TVP_TLG6_W_BLOCK_SIZE; + int ww = w; + if (step == -1) inbuf_index += ww-1; + if (0 != (i & 1)) inbuf_index += oddskip * ww; + +// byte IA = (byte)(inbuf[inbuf_index]>>24); +// byte IR = (byte)(inbuf[inbuf_index]>>16); +// byte IG = (byte)(inbuf[inbuf_index]>>8 ); +// byte IB = (byte)(inbuf[inbuf_index] ); + tvp_decoder decoder; + switch (filtertypes[filtertypes_index+i]) + { +// TVP_TLG6_DO_CHROMA_DECODE( 0, IB, IG, IR); + case 0: + decoder = (a, b, c, v) => tvp_med (a, b, c, v); + break; + case 1: + decoder = (a, b, c, v) => tvp_avg (a, b, c, v); + break; +// TVP_TLG6_DO_CHROMA_DECODE( 1, IB+IG, IG, IR+IG); + case 2: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (((v>>8)&0xff)<<8) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); + break; + case 3: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (((v>>8)&0xff)<<8) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); + break; +// TVP_TLG6_DO_CHROMA_DECODE( 2, IB, IG+IB, IR+IB+IG); + case 4: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 5: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; +// TVP_TLG6_DO_CHROMA_DECODE( 3, IB+IR+IG, IG+IR, IR); + case 6: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); + break; + case 7: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); + break; + case 8: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); + break; + case 9: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); + break; + case 10: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); + break; + case 11: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); + break; + case 12: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); + break; + case 13: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); + break; + case 14: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 15: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 16: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 17: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 18: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))) + ((v&0xff000000)))); + break; + case 19: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))) + ((v&0xff000000)))); + break; + case 20: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); + break; + case 21: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); + break; + case 22: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 23: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 24: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 25: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 26: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); + break; + case 27: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000)))); + break; + case 28: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); + break; + case 29: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))) + ((v&0xff000000)))); + break; +// TVP_TLG6_DO_CHROMA_DECODE(15, IB, IG+(IB<<1), IR+(IB<<1)); + case 30: + decoder = (a, b, c, v) => tvp_med (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+((v&0xff)<<1))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v&0xff)<<1))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + case 31: + decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint) + ((0xff0000 & ((((v>>16)&0xff)+((v&0xff)<<1))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v&0xff)<<1))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000)))); + break; + default: return; + } + do { + uint u = prevline[prevline_index]; + p = decoder (p, u, up, inbuf[inbuf_index]); + up = u; + curline[curline_index] = p; + curline_index++; + prevline_index++; + inbuf_index += step; + } while (0 != --w); + if (step == 1) + inbuf_index += skipblockbytes - ww; + else + inbuf_index += skipblockbytes + 1; + if (0 != (i&1)) inbuf_index -= oddskip * ww; + } + } + + static class TVP_Tables + { + public static byte[] TVPTLG6LeadingZeroTable = new byte[TVP_TLG6_LeadingZeroTable_SIZE]; + public static sbyte[,] TVPTLG6GolombBitLengthTable = new sbyte + [TVP_TLG6_GOLOMB_N_COUNT*2*128, TVP_TLG6_GOLOMB_N_COUNT]; + static short[,] TVPTLG6GolombCompressed = new short[TVP_TLG6_GOLOMB_N_COUNT,9] { + {3,7,15,27,63,108,223,448,130,}, + {3,5,13,24,51,95,192,384,257,}, + {2,5,12,21,39,86,155,320,384,}, + {2,3,9,18,33,61,129,258,511,}, + /* Tuned by W.Dee, 2004/03/25 */ + }; + + static TVP_Tables () + { +// TVPInitDitherTable(); + TVPTLG6InitLeadingZeroTable(); + TVPTLG6InitGolombTable(); + } + + static void TVPTLG6InitLeadingZeroTable () + { + /* table which indicates first set bit position + 1. */ + /* this may be replaced by BSF (IA32 instrcution). */ + + for (int i = 0; i < TVP_TLG6_LeadingZeroTable_SIZE; i++) + { + int cnt = 0; + int j; + for(j = 1; j != TVP_TLG6_LeadingZeroTable_SIZE && 0 == (i & j); + j <<= 1, cnt++); + cnt++; + if (j == TVP_TLG6_LeadingZeroTable_SIZE) cnt = 0; + TVPTLG6LeadingZeroTable[i] = (byte)cnt; + } + } + + static void TVPTLG6InitGolombTable() + { + for (int n = 0; n < TVP_TLG6_GOLOMB_N_COUNT; n++) + { + int a = 0; + for (int i = 0; i < 9; i++) + { + for (int j = 0; j < TVPTLG6GolombCompressed[n,i]; j++) + TVPTLG6GolombBitLengthTable[a++,n] = (sbyte)i; + } + if(a != TVP_TLG6_GOLOMB_N_COUNT*2*128) + throw new Exception ("Invalid data initialization"); /* THIS MUST NOT BE EXECUETED! */ + /* (this is for compressed table data check) */ + } + } + } + + void TVPTLG6DecodeGolombValuesForFirst (uint[] pixelbuf, int pixel_count, byte[] bit_pool) + { + /* + decode values packed in "bit_pool". + values are coded using golomb code. + + "ForFirst" function do dword access to pixelbuf, + clearing with zero except for blue (least siginificant byte). + */ + int bit_pool_index = 0; + + int n = TVP_TLG6_GOLOMB_N_COUNT - 1; /* output counter */ + int a = 0; /* summary of absolute values of errors */ + + int bit_pos = 1; + bool zero = 0 == (bit_pool[bit_pool_index] & 1); + + for (int pixel = 0; pixel < pixel_count; ) + { + /* get running count */ + int count; + + { + uint t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; + int b = TVP_Tables.TVPTLG6LeadingZeroTable[t & (TVP_TLG6_LeadingZeroTable_SIZE-1)]; + int bit_count = b; + while (0 == b) + { + bit_count += TVP_TLG6_LeadingZeroTable_BITS; + bit_pos += TVP_TLG6_LeadingZeroTable_BITS; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; + b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; + bit_count += b; + } + bit_pos += b; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + + bit_count --; + count = 1 << bit_count; + count += ((LittleEndian.ToInt32 (bit_pool, bit_pool_index) >> (bit_pos)) & (count-1)); + + bit_pos += bit_count; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + } + if (zero) + { + /* zero values */ + + /* fill distination with zero */ + do { pixelbuf[pixel++] = 0; } while (0 != --count); + + zero = !zero; + } + else + { + /* non-zero values */ + + /* fill distination with glomb code */ + + do + { + int k = TVP_Tables.TVPTLG6GolombBitLengthTable[a,n]; + int v, sign; + + uint t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; + int bit_count; + int b; + if (0 != t) + { + b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; + bit_count = b; + while (0 == b) + { + bit_count += TVP_TLG6_LeadingZeroTable_BITS; + bit_pos += TVP_TLG6_LeadingZeroTable_BITS; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; + b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; + bit_count += b; + } + bit_count --; + } + else + { + bit_pool_index += 5; + bit_count = bit_pool[bit_pool_index-1]; + bit_pos = 0; + t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index); + b = 0; + } + + v = (int)((bit_count << k) + ((t >> b) & ((1<>= 1; + a += v; + pixelbuf[pixel++] = (byte)((v ^ sign) + sign + 1); + + bit_pos += b; + bit_pos += k; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + + if (--n < 0) + { + a >>= 1; + n = TVP_TLG6_GOLOMB_N_COUNT - 1; + } + } while (0 != --count); + zero = !zero; + } + } + } + + void TVPTLG6DecodeGolombValues (uint[] pixelbuf, int offset, int pixel_count, byte[] bit_pool) + { + /* + decode values packed in "bit_pool". + values are coded using golomb code. + */ + uint mask = (uint)~(0xff << offset); + int bit_pool_index = 0; + + int n = TVP_TLG6_GOLOMB_N_COUNT - 1; /* output counter */ + int a = 0; /* summary of absolute values of errors */ + + int bit_pos = 1; + bool zero = 0 == (bit_pool[bit_pool_index] & 1); + + for (int pixel = 0; pixel < pixel_count; ) + { + /* get running count */ + int count; + + { + uint t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; + int b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; + int bit_count = b; + while (0 == b) + { + bit_count += TVP_TLG6_LeadingZeroTable_BITS; + bit_pos += TVP_TLG6_LeadingZeroTable_BITS; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; + b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; + bit_count += b; + } + bit_pos += b; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + + bit_count --; + count = 1 << bit_count; + count += (int)((LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> (bit_pos)) & (count-1)); + + bit_pos += bit_count; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + } + if (zero) + { + /* zero values */ + + /* fill distination with zero */ + do { pixelbuf[pixel++] &= mask; } while (0 != --count); + + zero = !zero; + } + else + { + /* non-zero values */ + + /* fill distination with glomb code */ + + do + { + int k = TVP_Tables.TVPTLG6GolombBitLengthTable[a,n]; + int v, sign; + + uint t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; + int bit_count; + int b; + if (0 != t) + { + b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; + bit_count = b; + while (0 == b) + { + bit_count += TVP_TLG6_LeadingZeroTable_BITS; + bit_pos += TVP_TLG6_LeadingZeroTable_BITS; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index) >> bit_pos; + b = TVP_Tables.TVPTLG6LeadingZeroTable[t&(TVP_TLG6_LeadingZeroTable_SIZE-1)]; + bit_count += b; + } + bit_count --; + } + else + { + bit_pool_index += 5; + bit_count = bit_pool[bit_pool_index-1]; + bit_pos = 0; + t = LittleEndian.ToUInt32 (bit_pool, bit_pool_index); + b = 0; + } + + v = (int)((bit_count << k) + ((t >> b) & ((1<>= 1; + a += v; + uint c = (uint)((pixelbuf[pixel] & mask) | (uint)((byte)((v ^ sign) + sign + 1) << offset)); + pixelbuf[pixel++] = c; + + bit_pos += b; + bit_pos += k; + bit_pool_index += bit_pos >> 3; + bit_pos &= 7; + + if (--n < 0) + { + a >>= 1; + n = TVP_TLG6_GOLOMB_N_COUNT - 1; + } + } while (0 != --count); + zero = !zero; + } + } + } + } +} diff --git a/ArcFormats/KiriKiriCx.cs b/ArcFormats/KiriKiri/KiriKiriCx.cs similarity index 100% rename from ArcFormats/KiriKiriCx.cs rename to ArcFormats/KiriKiri/KiriKiriCx.cs diff --git a/ArcFormats/WidgetXP3.xaml b/ArcFormats/KiriKiri/WidgetXP3.xaml similarity index 98% rename from ArcFormats/WidgetXP3.xaml rename to ArcFormats/KiriKiri/WidgetXP3.xaml index cabb9bf9..84936832 100644 --- a/ArcFormats/WidgetXP3.xaml +++ b/ArcFormats/KiriKiri/WidgetXP3.xaml @@ -1,9 +1,9 @@ - - - + + + diff --git a/ArcFormats/WidgetXP3.xaml.cs b/ArcFormats/KiriKiri/WidgetXP3.xaml.cs similarity index 95% rename from ArcFormats/WidgetXP3.xaml.cs rename to ArcFormats/KiriKiri/WidgetXP3.xaml.cs index cec0c168..99c58076 100644 --- a/ArcFormats/WidgetXP3.xaml.cs +++ b/ArcFormats/KiriKiri/WidgetXP3.xaml.cs @@ -1,27 +1,27 @@ -using System.Windows; -using System.Windows.Controls; -using GameRes.Formats.KiriKiri; -using GameRes.Formats.Properties; -using GameRes.Formats.Strings; - - -namespace GameRes.Formats.GUI -{ - /// - /// Interaction logic for WidgetXP3.xaml - /// - public partial class WidgetXP3 : Grid - { - public WidgetXP3 () - { - InitializeComponent(); - if (null == Scheme.SelectedItem) - Scheme.SelectedItem = arcStrings.ArcNoEncryption; - } - - public ICrypt GetScheme () - { - return Xp3Opener.GetScheme (Scheme.SelectedItem as string); - } - } -} +using System.Windows; +using System.Windows.Controls; +using GameRes.Formats.KiriKiri; +using GameRes.Formats.Properties; +using GameRes.Formats.Strings; + + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetXP3.xaml + /// + public partial class WidgetXP3 : Grid + { + public WidgetXP3 () + { + InitializeComponent(); + if (null == Scheme.SelectedItem) + Scheme.SelectedItem = arcStrings.ArcNoEncryption; + } + + public ICrypt GetScheme () + { + return Xp3Opener.GetScheme (Scheme.SelectedItem as string); + } + } +} diff --git a/ArcFormats/ArcXFL.cs b/ArcFormats/Liar/ArcXFL.cs similarity index 97% rename from ArcFormats/ArcXFL.cs rename to ArcFormats/Liar/ArcXFL.cs index c5a83ffc..c612b1cb 100644 --- a/ArcFormats/ArcXFL.cs +++ b/ArcFormats/Liar/ArcXFL.cs @@ -1,312 +1,312 @@ -//! \file ArcXFL.cs -//! \date Mon Jun 30 21:18:29 2014 -//! \brief XFL resource 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.IO; -using System.IO.MemoryMappedFiles; -using System.Text; -using System.Linq; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using GameRes.Formats.Strings; - -namespace GameRes.Formats.Liar -{ - [Export(typeof(ArchiveFormat))] - public class XflOpener : ArchiveFormat - { - public override string Tag { get { return "XFL"; } } - public override string Description { get { return Strings.arcStrings.XFLDescription; } } - public override uint Signature { get { return 0x0001424c; } } - public override bool IsHierarchic { get { return false; } } - public override bool CanCreate { get { return true; } } - - public override ArcFile TryOpen (ArcView file) - { - uint dir_size = file.View.ReadUInt32 (4); - int count = file.View.ReadInt32 (8); - if (count <= 0) - return null; - long max_offset = file.MaxOffset; - uint base_offset = dir_size + 12; - if (dir_size >= max_offset || base_offset >= max_offset) - return null; - - file.View.Reserve (0, base_offset); - long cur_offset = 12; - - var dir = new List (count); - for (int i = 0; i < count; ++i) - { - if (cur_offset+40 > base_offset) - return null; - string name = file.View.ReadString (cur_offset, 32); - var entry = FormatCatalog.Instance.CreateEntry (name); - entry.Offset = base_offset + file.View.ReadUInt32 (cur_offset+32); - entry.Size = file.View.ReadUInt32 (cur_offset+36); - if (!entry.CheckPlacement (max_offset)) - return null; - dir.Add (entry); - cur_offset += 40; - } - return new ArcFile (file, this, dir); - } - - public override void Create (Stream output, IEnumerable list, ResourceOptions options, - EntryCallback callback) - { - using (var writer = new BinaryWriter (output, Encoding.ASCII, true)) - { - writer.Write (Signature); - int list_size = list.Count(); - uint dir_size = (uint)(list_size * 40); - writer.Write (dir_size); - writer.Write (list_size); - - var encoding = Encodings.cp932.WithFatalFallback(); - - byte[] name_buf = new byte[32]; - int callback_count = 0; - - if (null != callback) - callback (callback_count++, null, arcStrings.MsgWritingIndex); - - // 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); - if (size < name_buf.Length) - name_buf[size] = 0; - } - 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 (8, SeekOrigin.Current); - } - - // now, write files and remember offset/sizes - uint current_offset = 0; - foreach (var entry in list) - { - if (null != callback) - callback (callback_count++, entry, arcStrings.MsgAddingFile); - - entry.Offset = current_offset; - using (var input = File.Open (entry.Name, FileMode.Open, FileAccess.Read)) - { - var size = input.Length; - if (size > uint.MaxValue || current_offset + size > uint.MaxValue) - throw new FileSizeException(); - current_offset += (uint)size; - entry.Size = (uint)size; - input.CopyTo (output); - } - } - - if (null != callback) - callback (callback_count++, null, arcStrings.MsgUpdatingIndex); - - // at last, go back to directory and write offset/sizes - long dir_offset = 12+32; - foreach (var entry in list) - { - writer.BaseStream.Position = dir_offset; - writer.Write ((uint)entry.Offset); - writer.Write (entry.Size); - dir_offset += 40; - } - } - } - } - - public class LwgImageEntry : ImageEntry - { - public int PosX; - public int PosY; - public int BPP; - } - - [Export(typeof(ArchiveFormat))] - public class LwgOpener : ArchiveFormat - { - public override string Tag { get { return "LWG"; } } - public override string Description { get { return Strings.arcStrings.LWGDescription; } } - public override uint Signature { get { return 0x0001474c; } } - public override bool IsHierarchic { get { return false; } } - - public override ArcFile TryOpen (ArcView file) - { - uint height = file.View.ReadUInt32 (4); - uint width = file.View.ReadUInt32 (8); - int count = file.View.ReadInt32 (12); - if (count <= 0) - return null; - uint dir_size = file.View.ReadUInt32 (20); - uint cur_offset = 24; - uint data_offset = cur_offset + dir_size; - uint data_size = file.View.ReadUInt32 (data_offset); - data_offset += 4; - - var dir = new List (count); - for (int i = 0; i < count; ++i) - { - var entry = new LwgImageEntry(); - entry.PosX = file.View.ReadInt32 (cur_offset); - entry.PosY = file.View.ReadInt32 (cur_offset+4); - entry.BPP = file.View.ReadByte (cur_offset+8); - entry.Offset = data_offset + file.View.ReadUInt32 (cur_offset+9); - entry.Size = file.View.ReadUInt32 (cur_offset+13); - - uint name_length = file.View.ReadByte (cur_offset+17); - string name = file.View.ReadString (cur_offset+18, name_length, Encoding.ASCII); - entry.Name = name + ".wcg"; - cur_offset += 18+name_length; - if (cur_offset > dir_size+24) - return null; - if (entry.CheckPlacement (data_offset + data_size)) - dir.Add (entry); - } - return new ArcFile (file, this, dir); - } - } - - public class GscScriptData : ScriptData - { - public byte[] Header; - public byte[] Code; - public byte[] Footer; - } - - //[Export(typeof(ScriptFormat))] - public class GscFormat : ScriptFormat - { - public override string Tag { get { return "GSC"; } } - public override string Description { get { return Strings.arcStrings.GSCDescription; } } - public override uint Signature { get { return 0; } } - - public override ScriptData Read (string name, Stream stream) - { - using (var file = new BinaryReader (stream, Encodings.cp932, true)) - { - uint signature = file.ReadUInt32(); - if (signature != file.BaseStream.Length) - return null; - uint header_size = file.ReadUInt32(); - if (header_size > 0x24 || header_size < 0x14) - return null; - uint code_size = file.ReadUInt32(); - uint text_index_size = file.ReadUInt32(); - uint text_size = file.ReadUInt32(); - byte[] header_data = file.ReadBytes ((int)header_size-0x14); - byte[] code = file.ReadBytes ((int)code_size); - uint[] index = new uint[text_index_size/4]; - for (int i = 0; i < index.Length; ++i) - { - index[i] = file.ReadUInt32(); - if (index[i] >= text_size) - return null; - } - long text_offset = header_size + code_size + text_index_size; - - var script = new GscScriptData(); - script.Header = header_data; - script.Code = code; - for (int i = 0; i < index.Length; ++i) - { - file.BaseStream.Position = text_offset + index[i]; - string text = StreamExtension.ReadCString (file.BaseStream); - script.TextLines.Add (new ScriptLine { Id = (uint)i, Text = text }); - } - long footer_pos = text_offset + text_size; - file.BaseStream.Position = footer_pos; - script.Footer = new byte[file.BaseStream.Length - footer_pos]; - file.BaseStream.Read (script.Footer, 0, script.Footer.Length); - return script; - } - } - - public override void Write (Stream stream, ScriptData script_data) - { - var script = script_data as GscScriptData; - if (null == script) - throw new InvalidFormatException(); - using (var file = new BinaryWriter (stream, Encodings.cp932, true)) - { - long file_size_pos = file.BaseStream.Position; - file.Write ((int)0); - - file.Write ((int)(script.Header.Length + 0x14)); - file.Write ((int)script.Code.Length); - int line_count = script.TextLines.Count; - int text_index_size = line_count * 4; - file.Write (text_index_size); - - long text_size_pos = file.BaseStream.Position; - file.Write ((int)0); - if (0 < script.Header.Length) - file.Write (script.Header); - if (0 < script.Code.Length) - file.Write (script.Code); - - long text_index_pos = file.BaseStream.Position; - var index = new uint[line_count]; - file.BaseStream.Seek (text_index_size, SeekOrigin.Current); - int i = 0; - long text_pos = file.BaseStream.Position; - uint current_pos = 0; - foreach (var line in script.TextLines) - { - index[i] = current_pos; - var text = Encodings.cp932.GetBytes (line.Text); - file.Write (text); - file.Write ((byte)0); - ++i; - current_pos += (uint)text.Length + 1; - } - uint text_size = (uint)(file.BaseStream.Position - text_pos); - if (0 < script.Footer.Length) - file.Write (script.Footer); - uint file_size = (uint)(file.BaseStream.Position - file_size_pos); - file.BaseStream.Position = file_size_pos; - file.Write (file_size); - file.BaseStream.Position = text_size_pos; - file.Write (text_size); - file.BaseStream.Position = text_index_pos; - foreach (var offset in index) - file.Write (offset); - file.BaseStream.Position = file_size_pos + file_size; - } - } - } -} +//! \file ArcXFL.cs +//! \date Mon Jun 30 21:18:29 2014 +//! \brief XFL resource 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.IO; +using System.IO.MemoryMappedFiles; +using System.Text; +using System.Linq; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using GameRes.Formats.Strings; + +namespace GameRes.Formats.Liar +{ + [Export(typeof(ArchiveFormat))] + public class XflOpener : ArchiveFormat + { + public override string Tag { get { return "XFL"; } } + public override string Description { get { return Strings.arcStrings.XFLDescription; } } + public override uint Signature { get { return 0x0001424c; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return true; } } + + public override ArcFile TryOpen (ArcView file) + { + uint dir_size = file.View.ReadUInt32 (4); + int count = file.View.ReadInt32 (8); + if (count <= 0) + return null; + long max_offset = file.MaxOffset; + uint base_offset = dir_size + 12; + if (dir_size >= max_offset || base_offset >= max_offset) + return null; + + file.View.Reserve (0, base_offset); + long cur_offset = 12; + + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + if (cur_offset+40 > base_offset) + return null; + string name = file.View.ReadString (cur_offset, 32); + var entry = FormatCatalog.Instance.CreateEntry (name); + entry.Offset = base_offset + file.View.ReadUInt32 (cur_offset+32); + entry.Size = file.View.ReadUInt32 (cur_offset+36); + if (!entry.CheckPlacement (max_offset)) + return null; + dir.Add (entry); + cur_offset += 40; + } + return new ArcFile (file, this, dir); + } + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + using (var writer = new BinaryWriter (output, Encoding.ASCII, true)) + { + writer.Write (Signature); + int list_size = list.Count(); + uint dir_size = (uint)(list_size * 40); + writer.Write (dir_size); + writer.Write (list_size); + + var encoding = Encodings.cp932.WithFatalFallback(); + + byte[] name_buf = new byte[32]; + int callback_count = 0; + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + + // 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); + if (size < name_buf.Length) + name_buf[size] = 0; + } + 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 (8, SeekOrigin.Current); + } + + // now, write files and remember offset/sizes + uint current_offset = 0; + foreach (var entry in list) + { + if (null != callback) + callback (callback_count++, entry, arcStrings.MsgAddingFile); + + entry.Offset = current_offset; + using (var input = File.Open (entry.Name, FileMode.Open, FileAccess.Read)) + { + var size = input.Length; + if (size > uint.MaxValue || current_offset + size > uint.MaxValue) + throw new FileSizeException(); + current_offset += (uint)size; + entry.Size = (uint)size; + input.CopyTo (output); + } + } + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgUpdatingIndex); + + // at last, go back to directory and write offset/sizes + long dir_offset = 12+32; + foreach (var entry in list) + { + writer.BaseStream.Position = dir_offset; + writer.Write ((uint)entry.Offset); + writer.Write (entry.Size); + dir_offset += 40; + } + } + } + } + + public class LwgImageEntry : ImageEntry + { + public int PosX; + public int PosY; + public int BPP; + } + + [Export(typeof(ArchiveFormat))] + public class LwgOpener : ArchiveFormat + { + public override string Tag { get { return "LWG"; } } + public override string Description { get { return Strings.arcStrings.LWGDescription; } } + public override uint Signature { get { return 0x0001474c; } } + public override bool IsHierarchic { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + uint height = file.View.ReadUInt32 (4); + uint width = file.View.ReadUInt32 (8); + int count = file.View.ReadInt32 (12); + if (count <= 0) + return null; + uint dir_size = file.View.ReadUInt32 (20); + uint cur_offset = 24; + uint data_offset = cur_offset + dir_size; + uint data_size = file.View.ReadUInt32 (data_offset); + data_offset += 4; + + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new LwgImageEntry(); + entry.PosX = file.View.ReadInt32 (cur_offset); + entry.PosY = file.View.ReadInt32 (cur_offset+4); + entry.BPP = file.View.ReadByte (cur_offset+8); + entry.Offset = data_offset + file.View.ReadUInt32 (cur_offset+9); + entry.Size = file.View.ReadUInt32 (cur_offset+13); + + uint name_length = file.View.ReadByte (cur_offset+17); + string name = file.View.ReadString (cur_offset+18, name_length, Encoding.ASCII); + entry.Name = name + ".wcg"; + cur_offset += 18+name_length; + if (cur_offset > dir_size+24) + return null; + if (entry.CheckPlacement (data_offset + data_size)) + dir.Add (entry); + } + return new ArcFile (file, this, dir); + } + } + + public class GscScriptData : ScriptData + { + public byte[] Header; + public byte[] Code; + public byte[] Footer; + } + + //[Export(typeof(ScriptFormat))] + public class GscFormat : ScriptFormat + { + public override string Tag { get { return "GSC"; } } + public override string Description { get { return Strings.arcStrings.GSCDescription; } } + public override uint Signature { get { return 0; } } + + public override ScriptData Read (string name, Stream stream) + { + using (var file = new BinaryReader (stream, Encodings.cp932, true)) + { + uint signature = file.ReadUInt32(); + if (signature != file.BaseStream.Length) + return null; + uint header_size = file.ReadUInt32(); + if (header_size > 0x24 || header_size < 0x14) + return null; + uint code_size = file.ReadUInt32(); + uint text_index_size = file.ReadUInt32(); + uint text_size = file.ReadUInt32(); + byte[] header_data = file.ReadBytes ((int)header_size-0x14); + byte[] code = file.ReadBytes ((int)code_size); + uint[] index = new uint[text_index_size/4]; + for (int i = 0; i < index.Length; ++i) + { + index[i] = file.ReadUInt32(); + if (index[i] >= text_size) + return null; + } + long text_offset = header_size + code_size + text_index_size; + + var script = new GscScriptData(); + script.Header = header_data; + script.Code = code; + for (int i = 0; i < index.Length; ++i) + { + file.BaseStream.Position = text_offset + index[i]; + string text = StreamExtension.ReadCString (file.BaseStream); + script.TextLines.Add (new ScriptLine { Id = (uint)i, Text = text }); + } + long footer_pos = text_offset + text_size; + file.BaseStream.Position = footer_pos; + script.Footer = new byte[file.BaseStream.Length - footer_pos]; + file.BaseStream.Read (script.Footer, 0, script.Footer.Length); + return script; + } + } + + public override void Write (Stream stream, ScriptData script_data) + { + var script = script_data as GscScriptData; + if (null == script) + throw new InvalidFormatException(); + using (var file = new BinaryWriter (stream, Encodings.cp932, true)) + { + long file_size_pos = file.BaseStream.Position; + file.Write ((int)0); + + file.Write ((int)(script.Header.Length + 0x14)); + file.Write ((int)script.Code.Length); + int line_count = script.TextLines.Count; + int text_index_size = line_count * 4; + file.Write (text_index_size); + + long text_size_pos = file.BaseStream.Position; + file.Write ((int)0); + if (0 < script.Header.Length) + file.Write (script.Header); + if (0 < script.Code.Length) + file.Write (script.Code); + + long text_index_pos = file.BaseStream.Position; + var index = new uint[line_count]; + file.BaseStream.Seek (text_index_size, SeekOrigin.Current); + int i = 0; + long text_pos = file.BaseStream.Position; + uint current_pos = 0; + foreach (var line in script.TextLines) + { + index[i] = current_pos; + var text = Encodings.cp932.GetBytes (line.Text); + file.Write (text); + file.Write ((byte)0); + ++i; + current_pos += (uint)text.Length + 1; + } + uint text_size = (uint)(file.BaseStream.Position - text_pos); + if (0 < script.Footer.Length) + file.Write (script.Footer); + uint file_size = (uint)(file.BaseStream.Position - file_size_pos); + file.BaseStream.Position = file_size_pos; + file.Write (file_size); + file.BaseStream.Position = text_size_pos; + file.Write (text_size); + file.BaseStream.Position = text_index_pos; + foreach (var offset in index) + file.Write (offset); + file.BaseStream.Position = file_size_pos + file_size; + } + } + } +} diff --git a/ArcFormats/ImageWCG.cs b/ArcFormats/Liar/ImageWCG.cs similarity index 97% rename from ArcFormats/ImageWCG.cs rename to ArcFormats/Liar/ImageWCG.cs index 507d5e95..8c0255eb 100644 --- a/ArcFormats/ImageWCG.cs +++ b/ArcFormats/Liar/ImageWCG.cs @@ -1,690 +1,690 @@ -//! \file ImageWCG.cs -//! \date Sat Jul 19 23:07:32 2014 -//! \brief Liar-soft WCG image 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.IO; -using System.ComponentModel.Composition; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Collections.Generic; -using System.Text; -using GameRes.Utility; - -namespace GameRes.Formats.Liar -{ - [Export(typeof(ImageFormat))] - public class WcgFormat : ImageFormat - { - public override string Tag { get { return "WCG"; } } - public override string Description { get { return "Liar-soft proprietary image format"; } } - public override uint Signature { get { return 0x02714757; } } - - public WcgFormat () - { - Signatures = new uint[] { 0x02714757, 0xF2714757 }; - } - - public override ImageMetaData ReadMetaData (Stream stream) - { - if (0x57 != stream.ReadByte() || 0x47 != stream.ReadByte()) - return null; - using (var file = new BinaryReader (stream, Encoding.ASCII, true)) - { - uint flags = file.ReadUInt16(); - if (1 != (flags & 0x0f) || 0x20 != file.ReadByte() || 0 != file.ReadByte()) - return null; - var meta = new ImageMetaData(); - file.BaseStream.Position = 8; - meta.Width = file.ReadUInt32(); - meta.Height = file.ReadUInt32(); - meta.BPP = 32; - return meta; - } - } - - public override ImageData Read (Stream file, ImageMetaData info) - { - using (var reader = new Reader (file, info.Width * info.Height)) - { - reader.Unpack(); - return ImageData.Create (info, PixelFormats.Bgra32, null, reader.Data); - } - } - - public override void Write (Stream file, ImageData image) - { - Stream stream = file; - bool buffer_used = false; - if (!stream.CanSeek) - { - stream = new MemoryStream(); - buffer_used = true; - } - try - { - using (var writer = new Writer (stream, image.Bitmap)) - { - writer.Pack(); - } - if (buffer_used) - { - stream.Position = 0; - stream.CopyTo (file); - } - } - finally - { - if (buffer_used) - stream.Dispose(); - } - } - - internal class Reader : IDisposable - { - private byte[] m_data; - private BinaryReader m_input; - private uint m_input_size; - private ushort[] m_index; - - private uint m_next_ptr; - private uint m_next_size; - private uint m_src; - private uint m_src_size; - private uint m_dst_size; - private uint edi; - - private uint m_index_length_limit; - private uint m_bits; - - public byte[] Data { get { return m_data; } } - - public Reader (Stream file, uint pixel_size) - { - m_data = new byte[pixel_size*4]; - m_input_size = (uint)file.Length; - m_input = new BinaryReader (file, Encoding.ASCII, true); - } - - public void Unpack () - { - m_next_ptr = 16; - m_next_size = m_input_size-16; - if (Unpack (2)) - Unpack (0); - for (uint i = 3; i < m_data.Length; i += 4) - m_data[i] = (byte)~m_data[i]; - } - - private bool Unpack (uint offset) - { - m_src = m_next_ptr; - m_src_size = m_next_size; - m_dst_size = (uint)(m_data.Length / 4); - if (m_src_size < 12) - throw new InvalidFormatException ("Invalid file size"); - m_src_size -= 12; - m_input.BaseStream.Position = m_next_ptr; - uint unpacked_size = m_input.ReadUInt32(); - uint data_size = m_input.ReadUInt32(); - uint index_size = m_input.ReadUInt16(); // 8 - - if (unpacked_size != m_dst_size*2) - throw new InvalidFormatException ("Invalid image size"); - - if (0 == index_size || index_size*2 > m_src_size) - throw new InvalidFormatException ("Invalid palette size"); - - m_src_size -= index_size*2; - if (data_size > m_src_size) - throw new InvalidFormatException ("Invalid compressed data size"); - - uint data_pos = m_src + index_size*2 + 12; - edi = offset; - m_next_size = m_src_size - data_size; - m_next_ptr = data_pos + data_size; - m_src_size = data_size; - return DecodeStream (data_pos, index_size); - } - - void ReadIndex (uint index_size) - { - m_input.BaseStream.Position = m_src+12; - m_index = new ushort[index_size]; - for (int i = 0; i < index_size; ++i) - m_index[i] = m_input.ReadUInt16(); - } - - bool DecodeStream (uint data_pos, uint index_size) - { - ReadIndex (index_size); - m_input.BaseStream.Position = data_pos; - - bool small_index = index_size < 0x1002; - m_index_length_limit = small_index ? 0x06u : 0x0eu; - uint index_bit_length = small_index ? 3u : 4u; - m_bits = 0; - while (m_dst_size > 0) - { - uint dst_count = 1; - uint index_length = GetBits (index_bit_length, 0); - if (0 == index_length) - { - dst_count = GetBits (4, 0) + 2; - index_length = GetBits (index_bit_length, 0); - } - if (0 == index_length) - return false; // std::cerr << "zero index length\n"; - - uint index = GetIndex (index_length); - if (index >= index_size) - return false; // std::cerr << "invalid index\n"; - - if (dst_count > m_dst_size) - return false; - m_dst_size -= dst_count; - ushort word = m_index[index]; - do { - PutWord (word); // *(uint16_t*)edi = word; - edi += 4; - } while (0 != --dst_count); - } - return true; - } - - void PutWord (ushort word) - { - m_data[edi ] = (byte)(word & 0xff); - m_data[edi+1] = (byte)(word >> 8); - } - - uint GetNextBit () - { - m_bits <<= 1; - if (0 == (m_bits & 0xff)) - { - if (0 == m_src_size--) - throw new InvalidFormatException ("Unexpected end of file"); - m_bits = (uint)m_input.ReadByte() << 1; - m_bits |= 1; - } - return (m_bits >> 8) & 1; - } - - uint GetIndex (uint count) - { - if (0 == --count) - return GetNextBit(); - if (count < m_index_length_limit) - return GetBits (count, 1); - while (0 != GetNextBit()) - { - if (count >= 0x10) - throw new InvalidFormatException ("Invalid index count"); - ++count; - } - return GetBits (count, 1); - } - - uint GetBits (uint count, uint word) - { - do - { - word = (word << 1) | GetNextBit(); - } - while (0 != --count); - return word; - } - - #region IDisposable Members - bool disposed = false; - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - if (!disposed) - { - if (disposing) - m_input.Dispose(); - m_input = null; - m_data = null; - m_index = null; - disposed = true; - } - } - #endregion - } - - private class Writer : IDisposable - { - private BinaryWriter m_out; - private uint m_width; - private uint m_height; - private uint m_pixels; - private byte[] m_data; - - Dictionary m_index = new Dictionary(); - private uint m_base_length; - private uint m_base_index_length; - private int m_bits; - - public Writer (Stream stream, BitmapSource bitmap) - { - m_width = (uint)bitmap.PixelWidth; - m_height = (uint)bitmap.PixelHeight; - m_pixels = m_width*m_height; - if (bitmap.Format != PixelFormats.Bgra32) - { - var converted_bitmap = new FormatConvertedBitmap(); - converted_bitmap.BeginInit(); - converted_bitmap.Source = bitmap; - converted_bitmap.DestinationFormat = PixelFormats.Bgra32; - converted_bitmap.EndInit(); - bitmap = converted_bitmap; - } - m_data = new byte[m_pixels*4]; - bitmap.CopyPixels (m_data, bitmap.PixelWidth*4, 0); - m_out = new BinaryWriter (stream, Encoding.ASCII, true); - } - - public void Pack () - { - byte[] header = { (byte)'W', (byte)'G', 0x71, 2, 0x20, 0, 0, 0x40 }; - m_out.Write (header, 0, header.Length); - m_out.Write (m_width); - m_out.Write (m_height); - - Pack (1, 0xff00); - Pack (0, 0); - } - - ushort GetWord (int offset) - { - return (ushort)(m_data[offset*2] | m_data[offset*2+1] << 8); - } - - private void Pack (int data, ushort mask) - { - var header_pos = m_out.Seek (0, SeekOrigin.Current); - m_out.Seek (12, SeekOrigin.Current); - - BuildIndex (data, mask); - bool small_index = m_index.Count < 0x1002; - m_base_length = small_index ? 3u : 4u; - m_base_index_length = small_index ? 7u : 15u; - m_bits = 1; - - // encode - for (uint i = 0; i < m_pixels;) - { - ushort word = GetWord (data); - data += 2; - ++i; - ushort color = m_index[(ushort)(word^mask)]; - uint count = 1; - while (i < m_pixels) - { - if (word != GetWord (data)) - break; - ++count; - data += 2; - ++i; - if (0x11 == count) - break; - } - if (count > 1) - { - PutBits (m_base_length, 0); - PutBits (4, count-2); - } - PutIndex (color); - } - Flush(); - - var end_pos = m_out.Seek (0, SeekOrigin.Current); - uint data_size = (uint)(end_pos - header_pos - 12 - m_index.Count*2); - m_out.Seek ((int)header_pos, SeekOrigin.Begin); - m_out.Write (m_pixels*2u); - m_out.Write (data_size); - m_out.Write ((ushort)m_index.Count); - m_out.Write ((ushort)(small_index ? 7 : 14)); // 0x0e - m_out.Seek ((int)end_pos, SeekOrigin.Begin); - } - - void BuildIndex (int data, ushort mask) - { - m_index.Clear(); - uint[] freq_table = new uint[65536]; - for (var data_end = data + m_pixels*2; data < data_end; data += 2) - freq_table[GetWord (data)^mask]++; - - var index = new List(); - for (int i = 0; i < freq_table.Length; ++i) - { - if (0 != freq_table[i]) - index.Add ((ushort)i); - } - index.Sort ((a, b) => freq_table[a] < freq_table[b] ? 1 : freq_table[a] == freq_table[b] ? 0 : -1); - ushort j = 0; - foreach (var color in index) - { - m_out.Write (color); - m_index.Add (color, j++); - } - } - - void Flush () - { - if (1 != m_bits) - { - do - m_bits <<= 1; - while (0 == (m_bits & 0x100)); - m_out.Write ((byte)(m_bits & 0xff)); - m_bits = 1; - } - } - - void PutBit (bool bit) - { - m_bits <<= 1; - m_bits |= bit ? 1 : 0; - if (0 != (m_bits & 0x100)) - { - m_out.Write ((byte)(m_bits & 0xff)); - m_bits = 1; - } - } - - void PutBits (uint length, uint x) - { - x <<= (int)(32-length); - while (0 != length--) - { - PutBit (0 != (x & 0x80000000)); - x <<= 1; - } - } - - static uint GetBitsLength (ushort val) - { - uint length = 0; - do - { - ++length; - val >>= 1; - } - while (0 != val); - return length; - } - - void PutIndex (ushort index) - { - uint length = GetBitsLength (index); - - if (length < m_base_index_length) - { - PutBits (m_base_length, length); - if (1 == length) - PutBit (index != 0); - else - PutBits (length-1, index); - } - else - { - PutBits (m_base_length, m_base_index_length); - for (uint i = m_base_index_length; i < length; ++i) - PutBit (true); - PutBit (false); - PutBits (length-1, index); - } - } - - #region IDisposable Members - bool disposed = false; - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - if (!disposed) - { - if (disposing) - m_out.Dispose(); - m_out = null; - m_data = null; - m_index = null; - disposed = true; - } - } - #endregion - } - } - - [Export(typeof(ImageFormat))] - public class LimFormat : ImageFormat - { - public override string Tag { get { return "LIM"; } } - public override string Description { get { return "Liar-soft image format"; } } - public override uint Signature { get { return 0; } } - - public override ImageMetaData ReadMetaData (Stream stream) - { - if (0x4C != stream.ReadByte() || 0x4D != stream.ReadByte()) - return null; - int flag = stream.ReadByte() & 0xF; - if (flag != 2 && flag != 3) - return null; - stream.Seek (5, SeekOrigin.Current); - using (var file = new ArcView.Reader (stream)) - { - var meta = new ImageMetaData { BPP = 32 }; - meta.Width = file.ReadUInt32(); - meta.Height = file.ReadUInt32(); - return meta; - } - } - - public override ImageData Read (Stream file, ImageMetaData info) - { - file.Position = 0x10; - using (var reader = new Reader (file, info)) - { - reader.Unpack(); - return ImageData.Create (info, reader.Format, null, reader.Data); - } - } - - public override void Write (Stream file, ImageData image) - { - throw new NotImplementedException ("LimFormat.Write not implemented"); - } - - internal class Reader : IDisposable - { - BinaryReader m_input; - byte[] m_output; - byte[] m_index; - byte[] m_image; - int m_width; - int m_height; - - public byte[] Data { get { return m_image; } } - public PixelFormat Format { get { return PixelFormats.Bgra32; } } - - public Reader (Stream file, ImageMetaData info) - { - m_input = new ArcView.Reader (file); - m_width = (int)info.Width; - m_height = (int)info.Height; - m_image = new byte[m_width*m_height*4]; - } - - int m_remaining; - int m_current; - int m_bits; - - public void Unpack () - { - byte mask = 0xFF; - for (int i = 3; i >= 0; --i) - { - int channel_size = m_input.ReadInt32(); - if (null == m_output) - m_output = new byte[channel_size]; - else if (channel_size != m_output.Length) - throw new InvalidFormatException(); - - m_remaining = m_input.ReadInt32(); - int index_size = m_input.ReadUInt16(); - if (null == m_index || index_size > m_index.Length) - m_index = new byte[index_size]; - m_input.ReadInt16(); // ignored - if (m_index.Length != m_input.Read (m_index, 0, m_index.Length)) - throw new InvalidFormatException ("Unexpected end of file"); - - UnpackChannel(); - int src = 0; - for (int p = i; p < m_image.Length; p += 4) - { - m_image[p] = (byte)(m_output[src++] ^ mask); - } - mask = 0; - } - } - - void UnpackChannel () - { - m_current = 0; - int dst = 0; - while (dst < m_output.Length) - { - int bits = GetBits (3); - if (-1 == bits) - break; - - if (0 != bits) - { - int index = GetIndex (bits); - if (index < 0) - break; - if (dst + 1 >= m_output.Length) - break; - - m_output[dst++] = m_index[index]; - } - else - { - int count = GetBits (4); - if (-1 == count) - break; - - bits = GetBits (3); - if (-1 == bits) - break; - - int index = GetIndex (bits); - if (-1 == index) - break; - count += 2; - for (int i = 0; i < count; i++) - { - if (dst + 1 >= m_output.Length) - return; - m_output[dst++] = m_index[index]; - } - } - } - } - - private int GetBits (int n) - { - int v = 0; - while (n > 0) - { - if (0 == m_current) - { - m_bits = m_input.ReadByte(); - m_current = 8; - } - v <<= 1; - m_bits <<= 1; - v |= (m_bits >> 8) & 1; - --m_current; - --n; - } - return v; - } - - private int GetIndex (int bits) - { - if (bits < 7) - { - if (0 == bits) - return -1; - if (1 == bits--) - return GetBits (1); - return (1 << bits) | GetBits (bits); - } - for (int i = 6; i < 12; ++i) - { - bits = GetBits (1); - if (-1 == bits) - return -1; - if (0 == bits) - return (1 << i) | GetBits (i); - } - return -1; - } - - #region IDisposable Members - bool disposed = false; - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - if (!disposed) - { - if (disposing) - m_input.Dispose(); - } - } - #endregion - } - } -} +//! \file ImageWCG.cs +//! \date Sat Jul 19 23:07:32 2014 +//! \brief Liar-soft WCG image 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.IO; +using System.ComponentModel.Composition; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Collections.Generic; +using System.Text; +using GameRes.Utility; + +namespace GameRes.Formats.Liar +{ + [Export(typeof(ImageFormat))] + public class WcgFormat : ImageFormat + { + public override string Tag { get { return "WCG"; } } + public override string Description { get { return "Liar-soft proprietary image format"; } } + public override uint Signature { get { return 0x02714757; } } + + public WcgFormat () + { + Signatures = new uint[] { 0x02714757, 0xF2714757 }; + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + if (0x57 != stream.ReadByte() || 0x47 != stream.ReadByte()) + return null; + using (var file = new BinaryReader (stream, Encoding.ASCII, true)) + { + uint flags = file.ReadUInt16(); + if (1 != (flags & 0x0f) || 0x20 != file.ReadByte() || 0 != file.ReadByte()) + return null; + var meta = new ImageMetaData(); + file.BaseStream.Position = 8; + meta.Width = file.ReadUInt32(); + meta.Height = file.ReadUInt32(); + meta.BPP = 32; + return meta; + } + } + + public override ImageData Read (Stream file, ImageMetaData info) + { + using (var reader = new Reader (file, info.Width * info.Height)) + { + reader.Unpack(); + return ImageData.Create (info, PixelFormats.Bgra32, null, reader.Data); + } + } + + public override void Write (Stream file, ImageData image) + { + Stream stream = file; + bool buffer_used = false; + if (!stream.CanSeek) + { + stream = new MemoryStream(); + buffer_used = true; + } + try + { + using (var writer = new Writer (stream, image.Bitmap)) + { + writer.Pack(); + } + if (buffer_used) + { + stream.Position = 0; + stream.CopyTo (file); + } + } + finally + { + if (buffer_used) + stream.Dispose(); + } + } + + internal class Reader : IDisposable + { + private byte[] m_data; + private BinaryReader m_input; + private uint m_input_size; + private ushort[] m_index; + + private uint m_next_ptr; + private uint m_next_size; + private uint m_src; + private uint m_src_size; + private uint m_dst_size; + private uint edi; + + private uint m_index_length_limit; + private uint m_bits; + + public byte[] Data { get { return m_data; } } + + public Reader (Stream file, uint pixel_size) + { + m_data = new byte[pixel_size*4]; + m_input_size = (uint)file.Length; + m_input = new BinaryReader (file, Encoding.ASCII, true); + } + + public void Unpack () + { + m_next_ptr = 16; + m_next_size = m_input_size-16; + if (Unpack (2)) + Unpack (0); + for (uint i = 3; i < m_data.Length; i += 4) + m_data[i] = (byte)~m_data[i]; + } + + private bool Unpack (uint offset) + { + m_src = m_next_ptr; + m_src_size = m_next_size; + m_dst_size = (uint)(m_data.Length / 4); + if (m_src_size < 12) + throw new InvalidFormatException ("Invalid file size"); + m_src_size -= 12; + m_input.BaseStream.Position = m_next_ptr; + uint unpacked_size = m_input.ReadUInt32(); + uint data_size = m_input.ReadUInt32(); + uint index_size = m_input.ReadUInt16(); // 8 + + if (unpacked_size != m_dst_size*2) + throw new InvalidFormatException ("Invalid image size"); + + if (0 == index_size || index_size*2 > m_src_size) + throw new InvalidFormatException ("Invalid palette size"); + + m_src_size -= index_size*2; + if (data_size > m_src_size) + throw new InvalidFormatException ("Invalid compressed data size"); + + uint data_pos = m_src + index_size*2 + 12; + edi = offset; + m_next_size = m_src_size - data_size; + m_next_ptr = data_pos + data_size; + m_src_size = data_size; + return DecodeStream (data_pos, index_size); + } + + void ReadIndex (uint index_size) + { + m_input.BaseStream.Position = m_src+12; + m_index = new ushort[index_size]; + for (int i = 0; i < index_size; ++i) + m_index[i] = m_input.ReadUInt16(); + } + + bool DecodeStream (uint data_pos, uint index_size) + { + ReadIndex (index_size); + m_input.BaseStream.Position = data_pos; + + bool small_index = index_size < 0x1002; + m_index_length_limit = small_index ? 0x06u : 0x0eu; + uint index_bit_length = small_index ? 3u : 4u; + m_bits = 0; + while (m_dst_size > 0) + { + uint dst_count = 1; + uint index_length = GetBits (index_bit_length, 0); + if (0 == index_length) + { + dst_count = GetBits (4, 0) + 2; + index_length = GetBits (index_bit_length, 0); + } + if (0 == index_length) + return false; // std::cerr << "zero index length\n"; + + uint index = GetIndex (index_length); + if (index >= index_size) + return false; // std::cerr << "invalid index\n"; + + if (dst_count > m_dst_size) + return false; + m_dst_size -= dst_count; + ushort word = m_index[index]; + do { + PutWord (word); // *(uint16_t*)edi = word; + edi += 4; + } while (0 != --dst_count); + } + return true; + } + + void PutWord (ushort word) + { + m_data[edi ] = (byte)(word & 0xff); + m_data[edi+1] = (byte)(word >> 8); + } + + uint GetNextBit () + { + m_bits <<= 1; + if (0 == (m_bits & 0xff)) + { + if (0 == m_src_size--) + throw new InvalidFormatException ("Unexpected end of file"); + m_bits = (uint)m_input.ReadByte() << 1; + m_bits |= 1; + } + return (m_bits >> 8) & 1; + } + + uint GetIndex (uint count) + { + if (0 == --count) + return GetNextBit(); + if (count < m_index_length_limit) + return GetBits (count, 1); + while (0 != GetNextBit()) + { + if (count >= 0x10) + throw new InvalidFormatException ("Invalid index count"); + ++count; + } + return GetBits (count, 1); + } + + uint GetBits (uint count, uint word) + { + do + { + word = (word << 1) | GetNextBit(); + } + while (0 != --count); + return word; + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + m_input.Dispose(); + m_input = null; + m_data = null; + m_index = null; + disposed = true; + } + } + #endregion + } + + private class Writer : IDisposable + { + private BinaryWriter m_out; + private uint m_width; + private uint m_height; + private uint m_pixels; + private byte[] m_data; + + Dictionary m_index = new Dictionary(); + private uint m_base_length; + private uint m_base_index_length; + private int m_bits; + + public Writer (Stream stream, BitmapSource bitmap) + { + m_width = (uint)bitmap.PixelWidth; + m_height = (uint)bitmap.PixelHeight; + m_pixels = m_width*m_height; + if (bitmap.Format != PixelFormats.Bgra32) + { + var converted_bitmap = new FormatConvertedBitmap(); + converted_bitmap.BeginInit(); + converted_bitmap.Source = bitmap; + converted_bitmap.DestinationFormat = PixelFormats.Bgra32; + converted_bitmap.EndInit(); + bitmap = converted_bitmap; + } + m_data = new byte[m_pixels*4]; + bitmap.CopyPixels (m_data, bitmap.PixelWidth*4, 0); + m_out = new BinaryWriter (stream, Encoding.ASCII, true); + } + + public void Pack () + { + byte[] header = { (byte)'W', (byte)'G', 0x71, 2, 0x20, 0, 0, 0x40 }; + m_out.Write (header, 0, header.Length); + m_out.Write (m_width); + m_out.Write (m_height); + + Pack (1, 0xff00); + Pack (0, 0); + } + + ushort GetWord (int offset) + { + return (ushort)(m_data[offset*2] | m_data[offset*2+1] << 8); + } + + private void Pack (int data, ushort mask) + { + var header_pos = m_out.Seek (0, SeekOrigin.Current); + m_out.Seek (12, SeekOrigin.Current); + + BuildIndex (data, mask); + bool small_index = m_index.Count < 0x1002; + m_base_length = small_index ? 3u : 4u; + m_base_index_length = small_index ? 7u : 15u; + m_bits = 1; + + // encode + for (uint i = 0; i < m_pixels;) + { + ushort word = GetWord (data); + data += 2; + ++i; + ushort color = m_index[(ushort)(word^mask)]; + uint count = 1; + while (i < m_pixels) + { + if (word != GetWord (data)) + break; + ++count; + data += 2; + ++i; + if (0x11 == count) + break; + } + if (count > 1) + { + PutBits (m_base_length, 0); + PutBits (4, count-2); + } + PutIndex (color); + } + Flush(); + + var end_pos = m_out.Seek (0, SeekOrigin.Current); + uint data_size = (uint)(end_pos - header_pos - 12 - m_index.Count*2); + m_out.Seek ((int)header_pos, SeekOrigin.Begin); + m_out.Write (m_pixels*2u); + m_out.Write (data_size); + m_out.Write ((ushort)m_index.Count); + m_out.Write ((ushort)(small_index ? 7 : 14)); // 0x0e + m_out.Seek ((int)end_pos, SeekOrigin.Begin); + } + + void BuildIndex (int data, ushort mask) + { + m_index.Clear(); + uint[] freq_table = new uint[65536]; + for (var data_end = data + m_pixels*2; data < data_end; data += 2) + freq_table[GetWord (data)^mask]++; + + var index = new List(); + for (int i = 0; i < freq_table.Length; ++i) + { + if (0 != freq_table[i]) + index.Add ((ushort)i); + } + index.Sort ((a, b) => freq_table[a] < freq_table[b] ? 1 : freq_table[a] == freq_table[b] ? 0 : -1); + ushort j = 0; + foreach (var color in index) + { + m_out.Write (color); + m_index.Add (color, j++); + } + } + + void Flush () + { + if (1 != m_bits) + { + do + m_bits <<= 1; + while (0 == (m_bits & 0x100)); + m_out.Write ((byte)(m_bits & 0xff)); + m_bits = 1; + } + } + + void PutBit (bool bit) + { + m_bits <<= 1; + m_bits |= bit ? 1 : 0; + if (0 != (m_bits & 0x100)) + { + m_out.Write ((byte)(m_bits & 0xff)); + m_bits = 1; + } + } + + void PutBits (uint length, uint x) + { + x <<= (int)(32-length); + while (0 != length--) + { + PutBit (0 != (x & 0x80000000)); + x <<= 1; + } + } + + static uint GetBitsLength (ushort val) + { + uint length = 0; + do + { + ++length; + val >>= 1; + } + while (0 != val); + return length; + } + + void PutIndex (ushort index) + { + uint length = GetBitsLength (index); + + if (length < m_base_index_length) + { + PutBits (m_base_length, length); + if (1 == length) + PutBit (index != 0); + else + PutBits (length-1, index); + } + else + { + PutBits (m_base_length, m_base_index_length); + for (uint i = m_base_index_length; i < length; ++i) + PutBit (true); + PutBit (false); + PutBits (length-1, index); + } + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + m_out.Dispose(); + m_out = null; + m_data = null; + m_index = null; + disposed = true; + } + } + #endregion + } + } + + [Export(typeof(ImageFormat))] + public class LimFormat : ImageFormat + { + public override string Tag { get { return "LIM"; } } + public override string Description { get { return "Liar-soft image format"; } } + public override uint Signature { get { return 0; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + if (0x4C != stream.ReadByte() || 0x4D != stream.ReadByte()) + return null; + int flag = stream.ReadByte() & 0xF; + if (flag != 2 && flag != 3) + return null; + stream.Seek (5, SeekOrigin.Current); + using (var file = new ArcView.Reader (stream)) + { + var meta = new ImageMetaData { BPP = 32 }; + meta.Width = file.ReadUInt32(); + meta.Height = file.ReadUInt32(); + return meta; + } + } + + public override ImageData Read (Stream file, ImageMetaData info) + { + file.Position = 0x10; + using (var reader = new Reader (file, info)) + { + reader.Unpack(); + return ImageData.Create (info, reader.Format, null, reader.Data); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("LimFormat.Write not implemented"); + } + + internal class Reader : IDisposable + { + BinaryReader m_input; + byte[] m_output; + byte[] m_index; + byte[] m_image; + int m_width; + int m_height; + + public byte[] Data { get { return m_image; } } + public PixelFormat Format { get { return PixelFormats.Bgra32; } } + + public Reader (Stream file, ImageMetaData info) + { + m_input = new ArcView.Reader (file); + m_width = (int)info.Width; + m_height = (int)info.Height; + m_image = new byte[m_width*m_height*4]; + } + + int m_remaining; + int m_current; + int m_bits; + + public void Unpack () + { + byte mask = 0xFF; + for (int i = 3; i >= 0; --i) + { + int channel_size = m_input.ReadInt32(); + if (null == m_output) + m_output = new byte[channel_size]; + else if (channel_size != m_output.Length) + throw new InvalidFormatException(); + + m_remaining = m_input.ReadInt32(); + int index_size = m_input.ReadUInt16(); + if (null == m_index || index_size > m_index.Length) + m_index = new byte[index_size]; + m_input.ReadInt16(); // ignored + if (m_index.Length != m_input.Read (m_index, 0, m_index.Length)) + throw new InvalidFormatException ("Unexpected end of file"); + + UnpackChannel(); + int src = 0; + for (int p = i; p < m_image.Length; p += 4) + { + m_image[p] = (byte)(m_output[src++] ^ mask); + } + mask = 0; + } + } + + void UnpackChannel () + { + m_current = 0; + int dst = 0; + while (dst < m_output.Length) + { + int bits = GetBits (3); + if (-1 == bits) + break; + + if (0 != bits) + { + int index = GetIndex (bits); + if (index < 0) + break; + if (dst + 1 >= m_output.Length) + break; + + m_output[dst++] = m_index[index]; + } + else + { + int count = GetBits (4); + if (-1 == count) + break; + + bits = GetBits (3); + if (-1 == bits) + break; + + int index = GetIndex (bits); + if (-1 == index) + break; + count += 2; + for (int i = 0; i < count; i++) + { + if (dst + 1 >= m_output.Length) + return; + m_output[dst++] = m_index[index]; + } + } + } + } + + private int GetBits (int n) + { + int v = 0; + while (n > 0) + { + if (0 == m_current) + { + m_bits = m_input.ReadByte(); + m_current = 8; + } + v <<= 1; + m_bits <<= 1; + v |= (m_bits >> 8) & 1; + --m_current; + --n; + } + return v; + } + + private int GetIndex (int bits) + { + if (bits < 7) + { + if (0 == bits) + return -1; + if (1 == bits--) + return GetBits (1); + return (1 << bits) | GetBits (bits); + } + for (int i = 6; i < 12; ++i) + { + bits = GetBits (1); + if (-1 == bits) + return -1; + if (0 == bits) + return (1 << i) | GetBits (i); + } + return -1; + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + m_input.Dispose(); + } + } + #endregion + } + } +} diff --git a/ArcFormats/ArcABM.cs b/ArcFormats/Lilim/ArcABM.cs similarity index 100% rename from ArcFormats/ArcABM.cs rename to ArcFormats/Lilim/ArcABM.cs diff --git a/ArcFormats/ArcAOS.cs b/ArcFormats/Lilim/ArcAOS.cs similarity index 100% rename from ArcFormats/ArcAOS.cs rename to ArcFormats/Lilim/ArcAOS.cs diff --git a/ArcFormats/ImageABM.cs b/ArcFormats/Lilim/ImageABM.cs similarity index 100% rename from ArcFormats/ImageABM.cs rename to ArcFormats/Lilim/ImageABM.cs diff --git a/ArcFormats/ArcLPK.cs b/ArcFormats/Lucifen/ArcLPK.cs similarity index 100% rename from ArcFormats/ArcLPK.cs rename to ArcFormats/Lucifen/ArcLPK.cs diff --git a/ArcFormats/ImageELG.cs b/ArcFormats/Lucifen/ImageELG.cs similarity index 100% rename from ArcFormats/ImageELG.cs rename to ArcFormats/Lucifen/ImageELG.cs diff --git a/ArcFormats/WidgetLPK.xaml b/ArcFormats/Lucifen/WidgetLPK.xaml similarity index 100% rename from ArcFormats/WidgetLPK.xaml rename to ArcFormats/Lucifen/WidgetLPK.xaml diff --git a/ArcFormats/WidgetLPK.xaml.cs b/ArcFormats/Lucifen/WidgetLPK.xaml.cs similarity index 100% rename from ArcFormats/WidgetLPK.xaml.cs rename to ArcFormats/Lucifen/WidgetLPK.xaml.cs diff --git a/ArcFormats/ArcCCT.cs b/ArcFormats/Macromedia/ArcCCT.cs similarity index 100% rename from ArcFormats/ArcCCT.cs rename to ArcFormats/Macromedia/ArcCCT.cs diff --git a/ArcFormats/AudioEDIM.cs b/ArcFormats/Macromedia/AudioEDIM.cs similarity index 100% rename from ArcFormats/AudioEDIM.cs rename to ArcFormats/Macromedia/AudioEDIM.cs diff --git a/ArcFormats/ImageBITD.cs b/ArcFormats/Macromedia/ImageBITD.cs similarity index 100% rename from ArcFormats/ImageBITD.cs rename to ArcFormats/Macromedia/ImageBITD.cs diff --git a/ArcFormats/ArcMajiro.cs b/ArcFormats/Majiro/ArcMajiro.cs similarity index 100% rename from ArcFormats/ArcMajiro.cs rename to ArcFormats/Majiro/ArcMajiro.cs diff --git a/ArcFormats/ImageRCT.cs b/ArcFormats/Majiro/ImageRCT.cs similarity index 100% rename from ArcFormats/ImageRCT.cs rename to ArcFormats/Majiro/ImageRCT.cs diff --git a/ArcFormats/WidgetRCT.xaml b/ArcFormats/Majiro/WidgetRCT.xaml similarity index 100% rename from ArcFormats/WidgetRCT.xaml rename to ArcFormats/Majiro/WidgetRCT.xaml diff --git a/ArcFormats/WidgetRCT.xaml.cs b/ArcFormats/Majiro/WidgetRCT.xaml.cs similarity index 100% rename from ArcFormats/WidgetRCT.xaml.cs rename to ArcFormats/Majiro/WidgetRCT.xaml.cs diff --git a/ArcFormats/ArcLIB.cs b/ArcFormats/Malie/ArcLIB.cs similarity index 100% rename from ArcFormats/ArcLIB.cs rename to ArcFormats/Malie/ArcLIB.cs diff --git a/ArcFormats/ImageDZI.cs b/ArcFormats/Malie/ImageDZI.cs similarity index 100% rename from ArcFormats/ImageDZI.cs rename to ArcFormats/Malie/ImageDZI.cs diff --git a/ArcFormats/ImageMGF.cs b/ArcFormats/Malie/ImageMGF.cs similarity index 100% rename from ArcFormats/ImageMGF.cs rename to ArcFormats/Malie/ImageMGF.cs diff --git a/ArcFormats/ArcMBL.cs b/ArcFormats/Marble/ArcMBL.cs similarity index 100% rename from ArcFormats/ArcMBL.cs rename to ArcFormats/Marble/ArcMBL.cs diff --git a/ArcFormats/AudioWADY.cs b/ArcFormats/Marble/AudioWADY.cs similarity index 100% rename from ArcFormats/AudioWADY.cs rename to ArcFormats/Marble/AudioWADY.cs diff --git a/ArcFormats/ImagePRS.cs b/ArcFormats/Marble/ImagePRS.cs similarity index 100% rename from ArcFormats/ImagePRS.cs rename to ArcFormats/Marble/ImagePRS.cs diff --git a/ArcFormats/WidgetMBL.xaml b/ArcFormats/Marble/WidgetMBL.xaml similarity index 100% rename from ArcFormats/WidgetMBL.xaml rename to ArcFormats/Marble/WidgetMBL.xaml diff --git a/ArcFormats/WidgetMBL.xaml.cs b/ArcFormats/Marble/WidgetMBL.xaml.cs similarity index 100% rename from ArcFormats/WidgetMBL.xaml.cs rename to ArcFormats/Marble/WidgetMBL.xaml.cs diff --git a/ArcFormats/ArcMGD.cs b/ArcFormats/Masys/ArcMGD.cs similarity index 100% rename from ArcFormats/ArcMGD.cs rename to ArcFormats/Masys/ArcMGD.cs diff --git a/ArcFormats/ImageAG.cs b/ArcFormats/Masys/ImageAG.cs similarity index 100% rename from ArcFormats/ImageAG.cs rename to ArcFormats/Masys/ImageAG.cs diff --git a/ArcFormats/ArcMnoViolet.cs b/ArcFormats/MnoViolet/ArcMnoViolet.cs similarity index 100% rename from ArcFormats/ArcMnoViolet.cs rename to ArcFormats/MnoViolet/ArcMnoViolet.cs diff --git a/ArcFormats/ImageMNV.cs b/ArcFormats/MnoViolet/ImageGRA.cs similarity index 100% rename from ArcFormats/ImageMNV.cs rename to ArcFormats/MnoViolet/ImageGRA.cs diff --git a/ArcFormats/ArcNSA.cs b/ArcFormats/NScripter/ArcNSA.cs similarity index 100% rename from ArcFormats/ArcNSA.cs rename to ArcFormats/NScripter/ArcNSA.cs diff --git a/ArcFormats/CreateONSWidget.xaml b/ArcFormats/NScripter/CreateONSWidget.xaml similarity index 100% rename from ArcFormats/CreateONSWidget.xaml rename to ArcFormats/NScripter/CreateONSWidget.xaml diff --git a/ArcFormats/CreateONSWidget.xaml.cs b/ArcFormats/NScripter/CreateONSWidget.xaml.cs similarity index 100% rename from ArcFormats/CreateONSWidget.xaml.cs rename to ArcFormats/NScripter/CreateONSWidget.xaml.cs diff --git a/ArcFormats/ArcNPA.cs b/ArcFormats/NitroPlus/ArcNPA.cs similarity index 97% rename from ArcFormats/ArcNPA.cs rename to ArcFormats/NitroPlus/ArcNPA.cs index 39a3c4d1..dd3d8981 100644 --- a/ArcFormats/ArcNPA.cs +++ b/ArcFormats/NitroPlus/ArcNPA.cs @@ -1,763 +1,763 @@ -//! \file ArcNPA.cs -//! \date Fri Jul 18 04:07:42 2014 -//! \brief NPA 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.IO; -using System.Linq; -using System.Text; -using System.ComponentModel.Composition; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using GameRes.Compression; -using GameRes.Formats.Strings; -using GameRes.Formats.Properties; - -namespace GameRes.Formats.NitroPlus -{ - internal class NpaEntry : PackedEntry - { - public byte[] RawName; - public int FolderId; - } - - internal class NpaArchive : ArcFile - { - public NpaTitleId GameId { get; private set; } - public int Key { get; private set; } - public byte[] KeyTable { get { return m_key_table.Value; } } - - private Lazy m_key_table; - - public NpaArchive (ArcView arc, ArchiveFormat impl, ICollection dir, - NpaTitleId game_id, int key) - : base (arc, impl, dir) - { - GameId = game_id; - Key = key; - m_key_table = new Lazy (() => NpaOpener.GenerateKeyTable (game_id)); - } - } - - public enum NpaTitleId - { - NotEncrypted, - CHAOSHEAD, CHAOSHEADTR1, CHAOSHEADTR2, MURAMASATR, MURAMASA, SUMAGA, DJANGO, DJANGOTR, - LAMENTO, SWEETPOOL, SUMAGASP, DEMONBANE, MURAMASAAD, AXANAEL, KIKOKUGAI, SONICOMITR2, - SUMAGA3P, SONICOMI, LOSTX, LOSTXTRAILER, DRAMATICALMURDER, TOTONO, PHENOMENO, NEKODA, - } - - public class NpaOptions : ResourceOptions - { - public NpaTitleId TitleId { get; set; } - public bool CompressContents { get; set; } - public int Key1 { get; set; } - public int Key2 { get; set; } - } - - [Export(typeof(ArchiveFormat))] - public class NpaOpener : ArchiveFormat - { - public override string Tag { get { return "NPA"; } } - public override string Description { get { return arcStrings.NPADescription; } } - public override uint Signature { get { return 0x0141504e; } } // NPA\x01 - public override bool IsHierarchic { get { return true; } } - public override bool CanCreate { get { return true; } } - - /// - /// Known encryption schemes. - /// Order should match NpaTitleId enumeration. - /// - public static readonly string[] KnownSchemes = new string[] { - arcStrings.ArcNoEncryption, - "Chaos;Head", "Chaos;Head Trial 1", "Chaos;Head Trial 2", "Muramasa Trial", "Muramasa", - "Sumaga", "Zoku Satsuriku no Django", "Zoku Satsuriku no Django Trial", "Lamento", - "Sweet Pool", "Sumaga Special", "Demonbane", "MuramasaAD", "Axanael", - "Kikokugai", "Sonicomi Trial 2", "Sumaga 3% Trial", "Sonicomi Version 1.0", - "Guilty Crown Lost Xmas", "Guilty Crown Lost Xmas Trailer", "DRAMAtical Murder", - "Kimi to Kanojo to Kanojo no Koi", "Phenomeno", "Nekoda -Nyanda-", - }; - - public const int DefaultKey1 = 0x4147414e; - public const int DefaultKey2 = 0x21214f54; - - public override ArcFile TryOpen (ArcView file) - { - int key1 = file.View.ReadInt32 (7); - int key2 = file.View.ReadInt32 (11); - bool compressed = 0 != file.View.ReadByte (15); - bool encrypted = 0 != file.View.ReadByte (16); - int total_count = file.View.ReadInt32 (17); - int folder_count = file.View.ReadInt32 (21); - int file_count = file.View.ReadInt32 (25); - if (total_count < folder_count + file_count) - return null; - uint dir_size = file.View.ReadUInt32 (37); - if (dir_size >= file.MaxOffset) - return null; - - var game_id = NpaTitleId.NotEncrypted; - if (encrypted) - game_id = QueryGameEncryption(); - - int key = GetArchiveKey (game_id, key1, key2); - - long cur_offset = 41; - var dir = new List (file_count); - for (int i = 0; i < total_count; ++i) - { - int name_size = file.View.ReadInt32 (cur_offset); - if ((uint)name_size >= dir_size) - return null; - int type = file.View.ReadByte (cur_offset+4+name_size); - if (1 != type) // ignore directory entries - { - var raw_name = new byte[name_size]; - file.View.Read (cur_offset+4, raw_name, 0, (uint)name_size); - for (int x = 0; x < name_size; ++x) - raw_name[x] += DecryptName (x, i, key); - var info_offset = cur_offset + 5 + name_size; - - int id = file.View.ReadInt32 (info_offset); - uint offset = file.View.ReadUInt32 (info_offset+4); - uint size = file.View.ReadUInt32 (info_offset+8); - uint unpacked_size = file.View.ReadUInt32 (info_offset+12); - - var entry = new NpaEntry { - Name = Encodings.cp932.GetString (raw_name), - Offset = dir_size+offset+41, - Size = size, - UnpackedSize = unpacked_size, - RawName = raw_name, - FolderId = id, - }; - if (!entry.CheckPlacement (file.MaxOffset)) - return null; - entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name); - entry.IsPacked = compressed && entry.Type != "image"; - dir.Add (entry); - } - cur_offset += 4 + name_size + 17; - } - if (game_id != NpaTitleId.NotEncrypted) - return new NpaArchive (file, this, dir, game_id, key); - else - return new ArcFile (file, this, dir); - } - - public override void Create (Stream output, IEnumerable list, ResourceOptions options, - EntryCallback callback) - { - var npa_options = GetOptions (options); - int callback_count = 0; - - // build file index - var index = new Indexer (list, npa_options); - - output.Position = 41 + index.Size; - long data_offset = 0; - - // write files - foreach (var entry in index.Entries.Where (e => e.Type != "directory")) - { - if (data_offset > uint.MaxValue) - throw new FileSizeException(); - if (null != callback) - callback (callback_count++, entry, arcStrings.MsgAddingFile); - using (var file = File.OpenRead (entry.Name)) - { - var size = file.Length; - if (size > uint.MaxValue) - throw new FileSizeException(); - entry.Offset = data_offset; - entry.UnpackedSize = (uint)size; - Stream destination = output; - if (NpaTitleId.NotEncrypted != npa_options.TitleId) - destination = new EncryptedStream (output, entry, npa_options.TitleId, index.Key); - try - { - if (entry.IsPacked) - { - var start = destination.Position; - using (var zstream = new ZLibStream (destination, CompressionMode.Compress, - CompressionLevel.Level9, true)) - { - file.CopyTo (zstream); - } - entry.Size = (uint)(destination.Position - start); - } - else - { - file.CopyTo (destination); - entry.Size = entry.UnpackedSize; - } - } - finally - { - if (destination is EncryptedStream) - destination.Dispose(); - } - data_offset += entry.Size; - } - } - if (null != callback) - callback (callback_count++, null, arcStrings.MsgWritingIndex); - - output.Position = 0; - using (var header = new BinaryWriter (output, Encoding.ASCII, true)) - { - header.Write (Signature); - header.Write ((short)0); - header.Write ((byte)0); - header.Write (npa_options.Key1); - header.Write (npa_options.Key2); - header.Write (npa_options.CompressContents); - header.Write (npa_options.TitleId != NpaTitleId.NotEncrypted); - header.Write (index.TotalCount); - header.Write (index.FolderCount); - header.Write (index.FileCount); - header.Write ((long)0); - header.Write (index.Size); - int entry_number = 0; - foreach (var entry in index.Entries) - { - header.Write (entry.RawName.Length); - for (int i = 0; i < entry.RawName.Length; ++i) - { - header.Write ((byte)(entry.RawName[i] - DecryptName (i, entry_number, index.Key))); - } - header.Write ((byte)("directory" == entry.Type ? 1 : 2)); - header.Write (entry.FolderId); - header.Write ((uint)entry.Offset); - header.Write (entry.Size); - header.Write (entry.UnpackedSize); - ++entry_number; - } - } - } - - public override Stream OpenEntry (ArcFile arc, Entry entry) - { - Stream input; - if (arc is NpaArchive && entry is NpaEntry) - input = new EncryptedStream (arc as NpaArchive, entry as NpaEntry); - else - input = arc.File.CreateStream (entry.Offset, entry.Size); - return UnpackEntry (input, entry as PackedEntry); - } - - private Stream UnpackEntry (Stream input, PackedEntry entry) - { - if (null != entry && entry.IsPacked) - return new ZLibStream (input, CompressionMode.Decompress); - return input; - } - - internal static byte DecryptName (int index, int curfile, int arc_key) - { - int key = 0xFC*index; - - key -= arc_key >> 0x18; - key -= arc_key >> 0x10; - key -= arc_key >> 0x08; - key -= arc_key & 0xff; - - key -= curfile >> 0x18; - key -= curfile >> 0x10; - key -= curfile >> 0x08; - key -= curfile; - - return (byte)(key & 0xff); - } - - internal static int GetArchiveKey (NpaTitleId game_id, int key1, int key2) - { - if (NpaTitleId.LAMENTO == game_id) - return key1 + key2; - else - return key1 * key2; - } - - internal static byte GetKeyFromEntry (NpaEntry entry, NpaTitleId game_id, int arc_key) - { - int key; - switch (game_id) - { - case NpaTitleId.AXANAEL: - case NpaTitleId.KIKOKUGAI: - case NpaTitleId.SONICOMITR2: - case NpaTitleId.SONICOMI: - case NpaTitleId.LOSTX: - case NpaTitleId.DRAMATICALMURDER: - case NpaTitleId.PHENOMENO: - key = 0x20101118; - break; - case NpaTitleId.TOTONO: - key = 0x12345678; - break; - default: - key = unchecked((int)0x87654321); - break; - } - var name = entry.RawName; - for (int i = 0; i < name.Length; ++i) - key -= name[i]; - - key *= name.Length; - - if (game_id != NpaTitleId.LAMENTO) // if the game is not Lamento - { - key += arc_key; - key *= (int)entry.UnpackedSize; - } - return (byte)key; - } - - public static byte[] GenerateKeyTable (NpaTitleId title_id) - { - int index = (int)title_id; - if (index < 0 || index >= OrderTable.Length) - throw new ArgumentOutOfRangeException ("title_id", "Invalid title id specified"); - - byte[] order = OrderTable[index]; - if (null == order) - throw new ArgumentException ("Encryption key table not defined", "title_id"); - - var table = new byte[256]; - for (int i = 0; i < 256; ++i) - { - int edx = i << 4; - int dl = (edx + order[i & 0x0f]) & 0xff; - int dh = (edx + (order[i>>4] << 8)) & 0xff00; - edx = (dh | dl) >> 4; - var eax = BaseTable[i]; - table[eax] = (byte)(edx & 0xff); - } - for (int i = 17; i < order.Length; i+=2) - { - int ecx = order[i-1]; - int edx = order[i]; - byte tmp = table[ecx]; - table[ecx] = table[edx]; - table[edx] = tmp; - } - if (NpaTitleId.TOTONO == title_id) - { - var totono_table = new byte[256]; - for (int i = 0; i < 256; ++i) - { - byte r = table[i]; - r = table[r]; - r = table[r]; - totono_table[i] = (byte)~r; - } - table = totono_table; - } - return table; - } - - public override ResourceOptions GetDefaultOptions () - { - return new NpaOptions { - TitleId = GetTitleId (Settings.Default.NPAScheme), - CompressContents = Settings.Default.NPACompressContents, - Key1 = (int)Settings.Default.NPAKey1, - Key2 = (int)Settings.Default.NPAKey2, - }; - } - - public override object GetAccessWidget () - { - return new GUI.WidgetNPA(); - } - - public override object GetCreationWidget () - { - return new GUI.CreateNPAWidget(); - } - - NpaTitleId QueryGameEncryption () - { - var options = Query (arcStrings.ArcEncryptedNotice); - return options.TitleId; - } - - public static NpaTitleId GetTitleId (string title) - { - Debug.Assert (KnownSchemes.Length == OrderTable.Length, - "Number of known encryptions schemes does not match available order tables."); - var index = Array.IndexOf (KnownSchemes, title); - if (index != -1) - return (NpaTitleId)index; - else - return NpaTitleId.NotEncrypted; - } - - static readonly byte[] BaseTable = { - 0x6F,0x05,0x6A,0xBF,0xA1,0xC7,0x8E,0xFB,0xD4,0x2F,0x80,0x58,0x4A,0x17,0x3B,0xB1, - 0x89,0xEC,0xA0,0x9F,0xD3,0xFC,0xC2,0x04,0x68,0x03,0xF3,0x25,0xBE,0x24,0xF1,0xBD, - 0xB8,0x41,0xC9,0x27,0x0E,0xA3,0xD8,0x7F,0x5B,0x8F,0x16,0x49,0xAA,0xB2,0x18,0xA7, - 0x33,0xE4,0xDB,0x48,0xCA,0xDE,0xAE,0xCD,0x13,0x1F,0x15,0x2E,0x39,0xF5,0x1E,0xDD, - 0x0F,0x88,0x4C,0x98,0x36,0xB4,0x3F,0x09,0x83,0xFD,0x32,0xBA,0x14,0x30,0x7A,0x63, - 0xB9,0x56,0x95,0x61,0xCC,0x8B,0xEF,0xDA,0xE5,0x2C,0xDC,0x12,0x1A,0x67,0x23,0x50, - 0xD1,0xC3,0x7E,0x6D,0xB6,0x90,0x3C,0xB3,0x0B,0xE2,0x91,0x70,0xA8,0xDF,0x44,0xC4, - 0xF4,0x01,0x5C,0x10,0x06,0xE7,0x54,0x40,0x43,0x72,0x38,0xBC,0xE3,0x07,0xFA,0x34, - 0x02,0xA4,0xF7,0x74,0xA9,0x4D,0x42,0xA5,0x85,0x35,0x79,0xD2,0x76,0x97,0x45,0x4F, - 0x08,0x5A,0xB0,0xEE,0x51,0x73,0x69,0x9E,0x94,0x47,0x77,0x29,0xD9,0x64,0x11,0xEB, - 0x37,0xAC,0x20,0x62,0x9A,0x6B,0x9C,0x75,0x22,0x87,0xAB,0x78,0x53,0xC8,0x5D,0xAD, - 0x2A,0xF2,0xCB,0xB7,0x0D,0xED,0x86,0x55,0xFF,0x19,0x57,0xD7,0xD5,0x60,0xC6,0x3D, - 0xEA,0xC1,0x6C,0xE1,0xC0,0x65,0x84,0xC5,0xE0,0x3E,0x7D,0x28,0x66,0xAF,0x1C,0x9B, - 0xCF,0x81,0x4E,0x26,0x59,0x2B,0x5F,0x7B,0xE8,0x8D,0x52,0x7C,0xF8,0x82,0x0C,0xF9, - 0x8C,0xE9,0xB5,0xE6,0x31,0x93,0x46,0x5E,0x1D,0x1B,0x4B,0x71,0xD6,0x92,0x3A,0xA6, - 0x2D,0x00,0x9D,0xBB,0x6E,0xF0,0x99,0xCE,0x21,0x0A,0xD0,0xF6,0xFE,0xA2,0x8A,0x96, - }; - - static readonly byte[][] OrderTable = { - null, // NotEncrypted - // CHAOSHEAD - new byte[] { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, - // CHAOSHEADTR1 - new byte[] { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1e,0x4e,0x66,0xb6 }, - // CHAOSHEADTR2 - new byte[] { 0x05,0x05,0x05,0x05,0x05,0x0b,0x0b,0x0b,0x0b,0x0b,0x00,0x00,0x00,0x00,0x00,0x00 }, - // MURAMASATR - new byte[] { 0x3c,0xe0,0x2e,0x2f,0x20,0x2e,0x2f,0x20,0x8e,0x80,0x80,0xf2,0xf2,0xf2,0xfa,0xfc }, - // MURAMASA - new byte[] { 0x35,0x70,0x2e,0x66,0x67,0x65,0x66,0x67,0x85,0x89,0x89,0x3b,0x3b,0x8b,0x81,0x85 }, - // SUMAGA - new byte[] { 0x3c,0xe0,0x2e,0x2f,0x2f,0x2f,0x2f,0x20,0x8e,0x8f,0x8f,0xf2,0xf2,0xf2,0xfc,0xfc }, - // DJANGO - new byte[] { 0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0x1e,0x4e,0x66,0xb6 }, - // DJANGOTR - new byte[] { 0xed,0xee,0xee,0xef,0xed,0xee,0xee,0xee,0xfe,0xde,0xee,0xef,0xed,0xee,0xfe,0xdf,0x1e,0x4e,0x66,0xb6 }, - // LAMENTO - new byte[] { 0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0x1e,0x4e,0x66,0xb6 }, - // SWEETPOOL - new byte[] { 0x38,0x9c,0x2a,0x8b,0x8b,0x8b,0x8b,0x8c,0x8a,0x8b,0x8b,0xae,0xae,0xae,0xa8,0xa8 }, - // SUMAGASP - new byte[] { 0xab,0x6f,0x9d,0x9e,0x9f,0x9d,0x9e,0xaf,0x8d,0xff,0xff,0x71,0x71,0x71,0x79,0x7b }, - // DEMONBANE - new byte[] { 0x96,0xb9,0x47,0x48,0x99,0x97,0x9c,0xaa,0x88,0xca,0xea,0x73,0x73,0x7b,0xc9,0xc6 }, - // MURAMASAAD - new byte[] { 0x00,0x04,0x04,0x68,0x68,0x68,0x68,0x68,0x6f,0x6f,0x9f,0x96,0x96,0x96,0x96,0x9b }, - // AXANAEL - new byte[] { 0x08,0x0c,0x0c,0xc0,0xf0,0xf0,0xf0,0xf0,0xf7,0xf7,0xf7,0xfe,0xfe,0xfe,0xfe,0xf3 }, - // KIKOKUGAI - new byte[] { 0x0f,0x07,0x07,0x90,0xf7,0xf7,0xf7,0xf7,0xf2,0x47,0x47,0x49,0xc9,0xc9,0xc9,0xc3 }, - // SONICOMITR2 - new byte[] { 0x08,0x0a,0x0a,0x40,0xfa,0xfa,0x50,0x50,0x55,0xf7,0xf7,0xf9,0x29,0x2c,0x7c,0x73 }, - // SUMAGA3P - new byte[] { 0x0f,0xef,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff }, - // SONICOMI - new byte[] { 0x0e,0x0b,0x0e,0x77,0x2e,0x2e,0x80,0x86,0xb9,0x2e,0x2e,0x29,0x89,0x82,0xad,0xaa }, - // LOSTX - new byte[] { 0x38,0xba,0x4b,0x5b,0x55,0xae,0xee,0xe0,0x67,0x48,0x08,0x0a,0x6a,0x3d,0x32,0x8d }, - // LOSTXTRAILER - new byte[] { 0x34,0x7a,0xbb,0xcb,0x11,0x65,0xea,0x5c,0x27,0x0f,0xcf,0xc6,0x66,0x39,0x39,0xfd }, - // DRAMATICALMURDER - new byte[] { 0x05,0x0d,0x0d,0x13,0xb5,0x3d,0x8d,0x2d,0x20,0xc7,0xc7,0xcf,0x1f,0xef,0xef,0x48 }, - // TOTONO - new byte[] { 0x6e,0x60,0x90,0xac,0xb3,0xe3,0x83,0xd6,0xde,0x7a,0x7a,0x7f,0xef,0xbf,0xb2,0xd6 }, - // some installation npa - //new byte[] { 0xa9,0xd3,0x34,0x84,0xd6,0xea,0xaa,0xdc,0xa0,0x64,0x24,0x26,0xd6,0xae,0xae,0x76 }, - // PHENOMENO - new byte[] { 0x30,0x96,0xdb,0x2b,0x3d,0x81,0x02,0x74,0x47,0x2b,0xeb,0xee,0x6e,0x35,0x35,0x5d }, - // NEKODA - new byte[] { 0xdc,0xdc,0xec,0xcd,0xdb,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0x1e,0x4e,0x66,0xb6 }, - }; - } - - /// - /// Archive creation helper. - /// - internal class Indexer - { - List m_entries; - Encoding m_encoding = Encodings.cp932.WithFatalFallback(); - int m_key; - int m_size = 0; - int m_directory_count = 0; - int m_file_count = 0; - - public IEnumerable Entries { get { return m_entries; } } - - public int Key { get { return m_key; } } - public int Size { get { return m_size; } } - public int TotalCount { get { return m_entries.Count; } } - public int FolderCount { get { return m_directory_count; } } - public int FileCount { get { return m_file_count; } } - - public Indexer (IEnumerable source_list, NpaOptions options) - { - m_entries = new List (source_list.Count()); - m_key = NpaOpener.GetArchiveKey (options.TitleId, options.Key1, options.Key2); - - foreach (var entry in source_list) - { - string name = entry.Name; - try - { - var dir = Path.GetDirectoryName (name); - int folder_id = 0; - if (!string.IsNullOrEmpty (dir)) - folder_id = AddDirectory (dir); - - bool compress = options.CompressContents; - if (compress) // don't compress images - compress = !FormatCatalog.Instance.LookupFileName (name).OfType().Any(); - var npa_entry = new NpaEntry - { - Name = name, - IsPacked = compress, - RawName = m_encoding.GetBytes (name), - FolderId = folder_id, - }; - ++m_file_count; - AddEntry (npa_entry); - } - catch (EncoderFallbackException X) - { - throw new InvalidFileName (name, arcStrings.MsgIllegalCharacters, X); - } - } - } - - void AddEntry (NpaEntry entry) - { - m_entries.Add (entry); - m_size += 4 + entry.RawName.Length + 17; - } - - Dictionary m_directory_map = new Dictionary(); - - int AddDirectory (string dir) - { - int folder_id = 0; - if (m_directory_map.TryGetValue (dir, out folder_id)) - return folder_id; - string path = ""; - foreach (var component in dir.Split (Path.DirectorySeparatorChar)) - { - path = Path.Combine (path, component); - if (m_directory_map.TryGetValue (path, out folder_id)) - continue; - folder_id = ++m_directory_count; - m_directory_map[path] = folder_id; - - var npa_entry = new NpaEntry - { - Name = path, - Type = "directory", - Offset = 0, - Size = 0, - UnpackedSize = 0, - IsPacked = false, - RawName = m_encoding.GetBytes (path), - FolderId = folder_id, - }; - AddEntry (npa_entry); - } - return folder_id; - } - } - - /// - /// Stream class for files stored in encrypted NPA archives. - /// - internal class EncryptedStream : Stream - { - private Stream m_stream; - private Lazy m_encrypted; - private int m_encrypted_length; - private bool m_read_mode; - private long m_base_pos; - - public override bool CanRead { get { return m_read_mode && m_stream.CanRead; } } - public override bool CanSeek { get { return m_stream.CanSeek; } } - public override bool CanWrite { get { return !m_read_mode && m_stream.CanWrite; } } - public override long Length { get { return m_stream.Length - m_base_pos; } } - public override long Position - { - get { return m_stream.Position - m_base_pos; } - set { m_stream.Position = m_base_pos + value; } - } - - delegate byte CryptFunc (int index, byte value); - CryptFunc Encrypt; - - public EncryptedStream (NpaArchive arc, NpaEntry entry) - { - m_read_mode = true; - m_encrypted_length = GetEncryptedLength (entry, arc.GameId); - if (m_encrypted_length > entry.Size) - m_encrypted_length = (int)entry.Size; - int key = NpaOpener.GetKeyFromEntry (entry, arc.GameId, arc.Key); - - m_stream = arc.File.CreateStream (entry.Offset, entry.Size); - m_encrypted = new Lazy (() => InitEncrypted (key, arc.GameId, arc.KeyTable)); - m_base_pos = m_stream.Position; - } - - public EncryptedStream (Stream output, NpaEntry entry, NpaTitleId game_id, int arc_key) - { - m_read_mode = false; - m_encrypted_length = GetEncryptedLength (entry, game_id); - int key = NpaOpener.GetKeyFromEntry (entry, game_id, arc_key); - - m_stream = output; - m_encrypted = new Lazy (() => new byte[m_encrypted_length]); - m_base_pos = m_stream.Position; - - byte[] decrypt_table = NpaOpener.GenerateKeyTable (game_id); - byte[] encrypt_table = new byte[256]; - for (int i = 0; i < 256; ++i) - encrypt_table[decrypt_table[i]] = (byte)i; - - if (NpaTitleId.LAMENTO == game_id) - { - Encrypt = (i, x) => encrypt_table[(x + key) & 0xff]; - } - else - { - Encrypt = (i, x) => encrypt_table[(x + key + i) & 0xff]; - } - } - - int GetEncryptedLength (NpaEntry entry, NpaTitleId game_id) - { - int length = 0x1000; - if (game_id != NpaTitleId.LAMENTO) - length += entry.RawName.Length; - return length; - } - - byte[] InitEncrypted (int key, NpaTitleId game_id, byte[] key_table) - { - var position = Position; - if (0 != position) - Position = 0; - byte[] buffer = new byte[m_encrypted_length]; - m_encrypted_length = m_stream.Read (buffer, 0, m_encrypted_length); - Position = position; - - if (game_id == NpaTitleId.LAMENTO) - { - for (int i = 0; i < m_encrypted_length; i++) - buffer[i] = (byte)(key_table[buffer[i]] - key); - } - else - { - for (int i = 0; i < m_encrypted_length; i++) - buffer[i] = (byte)(key_table[buffer[i]] - key - i); - } - return buffer; - } - - #region System.IO.Stream methods - public override void Flush() - { - m_stream.Flush(); - } - - public override long Seek (long offset, SeekOrigin origin) - { - if (SeekOrigin.Begin == origin) - offset += m_base_pos; - offset = m_stream.Seek (offset, origin); - return offset - m_base_pos; - } - - public override void SetLength (long length) - { - throw new NotSupportedException ("EncryptedStream.SetLength is not supported"); - } - - public override int Read (byte[] buffer, int offset, int count) - { - var position = Position; - if (position >= m_encrypted_length) - return m_stream.Read (buffer, offset, count); - int read = Math.Min (m_encrypted_length - (int)position, count); - Buffer.BlockCopy (m_encrypted.Value, (int)position, buffer, offset, read); - m_stream.Seek (read, SeekOrigin.Current); - if (read < count) - { - read += m_stream.Read (buffer, offset+read, count-read); - } - return read; - } - - public override int ReadByte () - { - var position = Position; - if (position >= m_encrypted_length) - return m_stream.ReadByte(); - m_stream.Seek (1, SeekOrigin.Current); - return m_encrypted.Value[(int)position]; - } - - public override void Write (byte[] buffer, int offset, int count) - { - var position = Position; - if (position < m_encrypted_length) - { - int limit = (int)position + Math.Min (m_encrypted_length - (int)position, count); - for (int i = (int)position; i < limit; ++i, ++offset, --count) - { - m_encrypted.Value[i] = Encrypt (i, buffer[offset]); - } - m_stream.Write (m_encrypted.Value, (int)position, limit-(int)position); - } - if (count > 0) - m_stream.Write (buffer, offset, count); - } - - public override void WriteByte (byte value) - { - var position = Position; - if (position < m_encrypted_length) - value = Encrypt ((int)position, value); - m_stream.WriteByte (value); - } - #endregion - - #region IDisposable Members - bool disposed = false; - protected override void Dispose (bool disposing) - { - if (!disposed) - { - if (disposing && m_read_mode) - { - m_stream.Dispose(); - } - m_encrypted = null; - disposed = true; - base.Dispose (disposing); - } - } - #endregion - } -} +//! \file ArcNPA.cs +//! \date Fri Jul 18 04:07:42 2014 +//! \brief NPA 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.IO; +using System.Linq; +using System.Text; +using System.ComponentModel.Composition; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using GameRes.Compression; +using GameRes.Formats.Strings; +using GameRes.Formats.Properties; + +namespace GameRes.Formats.NitroPlus +{ + internal class NpaEntry : PackedEntry + { + public byte[] RawName; + public int FolderId; + } + + internal class NpaArchive : ArcFile + { + public NpaTitleId GameId { get; private set; } + public int Key { get; private set; } + public byte[] KeyTable { get { return m_key_table.Value; } } + + private Lazy m_key_table; + + public NpaArchive (ArcView arc, ArchiveFormat impl, ICollection dir, + NpaTitleId game_id, int key) + : base (arc, impl, dir) + { + GameId = game_id; + Key = key; + m_key_table = new Lazy (() => NpaOpener.GenerateKeyTable (game_id)); + } + } + + public enum NpaTitleId + { + NotEncrypted, + CHAOSHEAD, CHAOSHEADTR1, CHAOSHEADTR2, MURAMASATR, MURAMASA, SUMAGA, DJANGO, DJANGOTR, + LAMENTO, SWEETPOOL, SUMAGASP, DEMONBANE, MURAMASAAD, AXANAEL, KIKOKUGAI, SONICOMITR2, + SUMAGA3P, SONICOMI, LOSTX, LOSTXTRAILER, DRAMATICALMURDER, TOTONO, PHENOMENO, NEKODA, + } + + public class NpaOptions : ResourceOptions + { + public NpaTitleId TitleId { get; set; } + public bool CompressContents { get; set; } + public int Key1 { get; set; } + public int Key2 { get; set; } + } + + [Export(typeof(ArchiveFormat))] + public class NpaOpener : ArchiveFormat + { + public override string Tag { get { return "NPA"; } } + public override string Description { get { return arcStrings.NPADescription; } } + public override uint Signature { get { return 0x0141504e; } } // NPA\x01 + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return true; } } + + /// + /// Known encryption schemes. + /// Order should match NpaTitleId enumeration. + /// + public static readonly string[] KnownSchemes = new string[] { + arcStrings.ArcNoEncryption, + "Chaos;Head", "Chaos;Head Trial 1", "Chaos;Head Trial 2", "Muramasa Trial", "Muramasa", + "Sumaga", "Zoku Satsuriku no Django", "Zoku Satsuriku no Django Trial", "Lamento", + "Sweet Pool", "Sumaga Special", "Demonbane", "MuramasaAD", "Axanael", + "Kikokugai", "Sonicomi Trial 2", "Sumaga 3% Trial", "Sonicomi Version 1.0", + "Guilty Crown Lost Xmas", "Guilty Crown Lost Xmas Trailer", "DRAMAtical Murder", + "Kimi to Kanojo to Kanojo no Koi", "Phenomeno", "Nekoda -Nyanda-", + }; + + public const int DefaultKey1 = 0x4147414e; + public const int DefaultKey2 = 0x21214f54; + + public override ArcFile TryOpen (ArcView file) + { + int key1 = file.View.ReadInt32 (7); + int key2 = file.View.ReadInt32 (11); + bool compressed = 0 != file.View.ReadByte (15); + bool encrypted = 0 != file.View.ReadByte (16); + int total_count = file.View.ReadInt32 (17); + int folder_count = file.View.ReadInt32 (21); + int file_count = file.View.ReadInt32 (25); + if (total_count < folder_count + file_count) + return null; + uint dir_size = file.View.ReadUInt32 (37); + if (dir_size >= file.MaxOffset) + return null; + + var game_id = NpaTitleId.NotEncrypted; + if (encrypted) + game_id = QueryGameEncryption(); + + int key = GetArchiveKey (game_id, key1, key2); + + long cur_offset = 41; + var dir = new List (file_count); + for (int i = 0; i < total_count; ++i) + { + int name_size = file.View.ReadInt32 (cur_offset); + if ((uint)name_size >= dir_size) + return null; + int type = file.View.ReadByte (cur_offset+4+name_size); + if (1 != type) // ignore directory entries + { + var raw_name = new byte[name_size]; + file.View.Read (cur_offset+4, raw_name, 0, (uint)name_size); + for (int x = 0; x < name_size; ++x) + raw_name[x] += DecryptName (x, i, key); + var info_offset = cur_offset + 5 + name_size; + + int id = file.View.ReadInt32 (info_offset); + uint offset = file.View.ReadUInt32 (info_offset+4); + uint size = file.View.ReadUInt32 (info_offset+8); + uint unpacked_size = file.View.ReadUInt32 (info_offset+12); + + var entry = new NpaEntry { + Name = Encodings.cp932.GetString (raw_name), + Offset = dir_size+offset+41, + Size = size, + UnpackedSize = unpacked_size, + RawName = raw_name, + FolderId = id, + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name); + entry.IsPacked = compressed && entry.Type != "image"; + dir.Add (entry); + } + cur_offset += 4 + name_size + 17; + } + if (game_id != NpaTitleId.NotEncrypted) + return new NpaArchive (file, this, dir, game_id, key); + else + return new ArcFile (file, this, dir); + } + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + var npa_options = GetOptions (options); + int callback_count = 0; + + // build file index + var index = new Indexer (list, npa_options); + + output.Position = 41 + index.Size; + long data_offset = 0; + + // write files + foreach (var entry in index.Entries.Where (e => e.Type != "directory")) + { + if (data_offset > uint.MaxValue) + throw new FileSizeException(); + if (null != callback) + callback (callback_count++, entry, arcStrings.MsgAddingFile); + using (var file = File.OpenRead (entry.Name)) + { + var size = file.Length; + if (size > uint.MaxValue) + throw new FileSizeException(); + entry.Offset = data_offset; + entry.UnpackedSize = (uint)size; + Stream destination = output; + if (NpaTitleId.NotEncrypted != npa_options.TitleId) + destination = new EncryptedStream (output, entry, npa_options.TitleId, index.Key); + try + { + if (entry.IsPacked) + { + var start = destination.Position; + using (var zstream = new ZLibStream (destination, CompressionMode.Compress, + CompressionLevel.Level9, true)) + { + file.CopyTo (zstream); + } + entry.Size = (uint)(destination.Position - start); + } + else + { + file.CopyTo (destination); + entry.Size = entry.UnpackedSize; + } + } + finally + { + if (destination is EncryptedStream) + destination.Dispose(); + } + data_offset += entry.Size; + } + } + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + + output.Position = 0; + using (var header = new BinaryWriter (output, Encoding.ASCII, true)) + { + header.Write (Signature); + header.Write ((short)0); + header.Write ((byte)0); + header.Write (npa_options.Key1); + header.Write (npa_options.Key2); + header.Write (npa_options.CompressContents); + header.Write (npa_options.TitleId != NpaTitleId.NotEncrypted); + header.Write (index.TotalCount); + header.Write (index.FolderCount); + header.Write (index.FileCount); + header.Write ((long)0); + header.Write (index.Size); + int entry_number = 0; + foreach (var entry in index.Entries) + { + header.Write (entry.RawName.Length); + for (int i = 0; i < entry.RawName.Length; ++i) + { + header.Write ((byte)(entry.RawName[i] - DecryptName (i, entry_number, index.Key))); + } + header.Write ((byte)("directory" == entry.Type ? 1 : 2)); + header.Write (entry.FolderId); + header.Write ((uint)entry.Offset); + header.Write (entry.Size); + header.Write (entry.UnpackedSize); + ++entry_number; + } + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + Stream input; + if (arc is NpaArchive && entry is NpaEntry) + input = new EncryptedStream (arc as NpaArchive, entry as NpaEntry); + else + input = arc.File.CreateStream (entry.Offset, entry.Size); + return UnpackEntry (input, entry as PackedEntry); + } + + private Stream UnpackEntry (Stream input, PackedEntry entry) + { + if (null != entry && entry.IsPacked) + return new ZLibStream (input, CompressionMode.Decompress); + return input; + } + + internal static byte DecryptName (int index, int curfile, int arc_key) + { + int key = 0xFC*index; + + key -= arc_key >> 0x18; + key -= arc_key >> 0x10; + key -= arc_key >> 0x08; + key -= arc_key & 0xff; + + key -= curfile >> 0x18; + key -= curfile >> 0x10; + key -= curfile >> 0x08; + key -= curfile; + + return (byte)(key & 0xff); + } + + internal static int GetArchiveKey (NpaTitleId game_id, int key1, int key2) + { + if (NpaTitleId.LAMENTO == game_id) + return key1 + key2; + else + return key1 * key2; + } + + internal static byte GetKeyFromEntry (NpaEntry entry, NpaTitleId game_id, int arc_key) + { + int key; + switch (game_id) + { + case NpaTitleId.AXANAEL: + case NpaTitleId.KIKOKUGAI: + case NpaTitleId.SONICOMITR2: + case NpaTitleId.SONICOMI: + case NpaTitleId.LOSTX: + case NpaTitleId.DRAMATICALMURDER: + case NpaTitleId.PHENOMENO: + key = 0x20101118; + break; + case NpaTitleId.TOTONO: + key = 0x12345678; + break; + default: + key = unchecked((int)0x87654321); + break; + } + var name = entry.RawName; + for (int i = 0; i < name.Length; ++i) + key -= name[i]; + + key *= name.Length; + + if (game_id != NpaTitleId.LAMENTO) // if the game is not Lamento + { + key += arc_key; + key *= (int)entry.UnpackedSize; + } + return (byte)key; + } + + public static byte[] GenerateKeyTable (NpaTitleId title_id) + { + int index = (int)title_id; + if (index < 0 || index >= OrderTable.Length) + throw new ArgumentOutOfRangeException ("title_id", "Invalid title id specified"); + + byte[] order = OrderTable[index]; + if (null == order) + throw new ArgumentException ("Encryption key table not defined", "title_id"); + + var table = new byte[256]; + for (int i = 0; i < 256; ++i) + { + int edx = i << 4; + int dl = (edx + order[i & 0x0f]) & 0xff; + int dh = (edx + (order[i>>4] << 8)) & 0xff00; + edx = (dh | dl) >> 4; + var eax = BaseTable[i]; + table[eax] = (byte)(edx & 0xff); + } + for (int i = 17; i < order.Length; i+=2) + { + int ecx = order[i-1]; + int edx = order[i]; + byte tmp = table[ecx]; + table[ecx] = table[edx]; + table[edx] = tmp; + } + if (NpaTitleId.TOTONO == title_id) + { + var totono_table = new byte[256]; + for (int i = 0; i < 256; ++i) + { + byte r = table[i]; + r = table[r]; + r = table[r]; + totono_table[i] = (byte)~r; + } + table = totono_table; + } + return table; + } + + public override ResourceOptions GetDefaultOptions () + { + return new NpaOptions { + TitleId = GetTitleId (Settings.Default.NPAScheme), + CompressContents = Settings.Default.NPACompressContents, + Key1 = (int)Settings.Default.NPAKey1, + Key2 = (int)Settings.Default.NPAKey2, + }; + } + + public override object GetAccessWidget () + { + return new GUI.WidgetNPA(); + } + + public override object GetCreationWidget () + { + return new GUI.CreateNPAWidget(); + } + + NpaTitleId QueryGameEncryption () + { + var options = Query (arcStrings.ArcEncryptedNotice); + return options.TitleId; + } + + public static NpaTitleId GetTitleId (string title) + { + Debug.Assert (KnownSchemes.Length == OrderTable.Length, + "Number of known encryptions schemes does not match available order tables."); + var index = Array.IndexOf (KnownSchemes, title); + if (index != -1) + return (NpaTitleId)index; + else + return NpaTitleId.NotEncrypted; + } + + static readonly byte[] BaseTable = { + 0x6F,0x05,0x6A,0xBF,0xA1,0xC7,0x8E,0xFB,0xD4,0x2F,0x80,0x58,0x4A,0x17,0x3B,0xB1, + 0x89,0xEC,0xA0,0x9F,0xD3,0xFC,0xC2,0x04,0x68,0x03,0xF3,0x25,0xBE,0x24,0xF1,0xBD, + 0xB8,0x41,0xC9,0x27,0x0E,0xA3,0xD8,0x7F,0x5B,0x8F,0x16,0x49,0xAA,0xB2,0x18,0xA7, + 0x33,0xE4,0xDB,0x48,0xCA,0xDE,0xAE,0xCD,0x13,0x1F,0x15,0x2E,0x39,0xF5,0x1E,0xDD, + 0x0F,0x88,0x4C,0x98,0x36,0xB4,0x3F,0x09,0x83,0xFD,0x32,0xBA,0x14,0x30,0x7A,0x63, + 0xB9,0x56,0x95,0x61,0xCC,0x8B,0xEF,0xDA,0xE5,0x2C,0xDC,0x12,0x1A,0x67,0x23,0x50, + 0xD1,0xC3,0x7E,0x6D,0xB6,0x90,0x3C,0xB3,0x0B,0xE2,0x91,0x70,0xA8,0xDF,0x44,0xC4, + 0xF4,0x01,0x5C,0x10,0x06,0xE7,0x54,0x40,0x43,0x72,0x38,0xBC,0xE3,0x07,0xFA,0x34, + 0x02,0xA4,0xF7,0x74,0xA9,0x4D,0x42,0xA5,0x85,0x35,0x79,0xD2,0x76,0x97,0x45,0x4F, + 0x08,0x5A,0xB0,0xEE,0x51,0x73,0x69,0x9E,0x94,0x47,0x77,0x29,0xD9,0x64,0x11,0xEB, + 0x37,0xAC,0x20,0x62,0x9A,0x6B,0x9C,0x75,0x22,0x87,0xAB,0x78,0x53,0xC8,0x5D,0xAD, + 0x2A,0xF2,0xCB,0xB7,0x0D,0xED,0x86,0x55,0xFF,0x19,0x57,0xD7,0xD5,0x60,0xC6,0x3D, + 0xEA,0xC1,0x6C,0xE1,0xC0,0x65,0x84,0xC5,0xE0,0x3E,0x7D,0x28,0x66,0xAF,0x1C,0x9B, + 0xCF,0x81,0x4E,0x26,0x59,0x2B,0x5F,0x7B,0xE8,0x8D,0x52,0x7C,0xF8,0x82,0x0C,0xF9, + 0x8C,0xE9,0xB5,0xE6,0x31,0x93,0x46,0x5E,0x1D,0x1B,0x4B,0x71,0xD6,0x92,0x3A,0xA6, + 0x2D,0x00,0x9D,0xBB,0x6E,0xF0,0x99,0xCE,0x21,0x0A,0xD0,0xF6,0xFE,0xA2,0x8A,0x96, + }; + + static readonly byte[][] OrderTable = { + null, // NotEncrypted + // CHAOSHEAD + new byte[] { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, + // CHAOSHEADTR1 + new byte[] { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1e,0x4e,0x66,0xb6 }, + // CHAOSHEADTR2 + new byte[] { 0x05,0x05,0x05,0x05,0x05,0x0b,0x0b,0x0b,0x0b,0x0b,0x00,0x00,0x00,0x00,0x00,0x00 }, + // MURAMASATR + new byte[] { 0x3c,0xe0,0x2e,0x2f,0x20,0x2e,0x2f,0x20,0x8e,0x80,0x80,0xf2,0xf2,0xf2,0xfa,0xfc }, + // MURAMASA + new byte[] { 0x35,0x70,0x2e,0x66,0x67,0x65,0x66,0x67,0x85,0x89,0x89,0x3b,0x3b,0x8b,0x81,0x85 }, + // SUMAGA + new byte[] { 0x3c,0xe0,0x2e,0x2f,0x2f,0x2f,0x2f,0x20,0x8e,0x8f,0x8f,0xf2,0xf2,0xf2,0xfc,0xfc }, + // DJANGO + new byte[] { 0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0x1e,0x4e,0x66,0xb6 }, + // DJANGOTR + new byte[] { 0xed,0xee,0xee,0xef,0xed,0xee,0xee,0xee,0xfe,0xde,0xee,0xef,0xed,0xee,0xfe,0xdf,0x1e,0x4e,0x66,0xb6 }, + // LAMENTO + new byte[] { 0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0x1e,0x4e,0x66,0xb6 }, + // SWEETPOOL + new byte[] { 0x38,0x9c,0x2a,0x8b,0x8b,0x8b,0x8b,0x8c,0x8a,0x8b,0x8b,0xae,0xae,0xae,0xa8,0xa8 }, + // SUMAGASP + new byte[] { 0xab,0x6f,0x9d,0x9e,0x9f,0x9d,0x9e,0xaf,0x8d,0xff,0xff,0x71,0x71,0x71,0x79,0x7b }, + // DEMONBANE + new byte[] { 0x96,0xb9,0x47,0x48,0x99,0x97,0x9c,0xaa,0x88,0xca,0xea,0x73,0x73,0x7b,0xc9,0xc6 }, + // MURAMASAAD + new byte[] { 0x00,0x04,0x04,0x68,0x68,0x68,0x68,0x68,0x6f,0x6f,0x9f,0x96,0x96,0x96,0x96,0x9b }, + // AXANAEL + new byte[] { 0x08,0x0c,0x0c,0xc0,0xf0,0xf0,0xf0,0xf0,0xf7,0xf7,0xf7,0xfe,0xfe,0xfe,0xfe,0xf3 }, + // KIKOKUGAI + new byte[] { 0x0f,0x07,0x07,0x90,0xf7,0xf7,0xf7,0xf7,0xf2,0x47,0x47,0x49,0xc9,0xc9,0xc9,0xc3 }, + // SONICOMITR2 + new byte[] { 0x08,0x0a,0x0a,0x40,0xfa,0xfa,0x50,0x50,0x55,0xf7,0xf7,0xf9,0x29,0x2c,0x7c,0x73 }, + // SUMAGA3P + new byte[] { 0x0f,0xef,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff }, + // SONICOMI + new byte[] { 0x0e,0x0b,0x0e,0x77,0x2e,0x2e,0x80,0x86,0xb9,0x2e,0x2e,0x29,0x89,0x82,0xad,0xaa }, + // LOSTX + new byte[] { 0x38,0xba,0x4b,0x5b,0x55,0xae,0xee,0xe0,0x67,0x48,0x08,0x0a,0x6a,0x3d,0x32,0x8d }, + // LOSTXTRAILER + new byte[] { 0x34,0x7a,0xbb,0xcb,0x11,0x65,0xea,0x5c,0x27,0x0f,0xcf,0xc6,0x66,0x39,0x39,0xfd }, + // DRAMATICALMURDER + new byte[] { 0x05,0x0d,0x0d,0x13,0xb5,0x3d,0x8d,0x2d,0x20,0xc7,0xc7,0xcf,0x1f,0xef,0xef,0x48 }, + // TOTONO + new byte[] { 0x6e,0x60,0x90,0xac,0xb3,0xe3,0x83,0xd6,0xde,0x7a,0x7a,0x7f,0xef,0xbf,0xb2,0xd6 }, + // some installation npa + //new byte[] { 0xa9,0xd3,0x34,0x84,0xd6,0xea,0xaa,0xdc,0xa0,0x64,0x24,0x26,0xd6,0xae,0xae,0x76 }, + // PHENOMENO + new byte[] { 0x30,0x96,0xdb,0x2b,0x3d,0x81,0x02,0x74,0x47,0x2b,0xeb,0xee,0x6e,0x35,0x35,0x5d }, + // NEKODA + new byte[] { 0xdc,0xdc,0xec,0xcd,0xdb,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0x1e,0x4e,0x66,0xb6 }, + }; + } + + /// + /// Archive creation helper. + /// + internal class Indexer + { + List m_entries; + Encoding m_encoding = Encodings.cp932.WithFatalFallback(); + int m_key; + int m_size = 0; + int m_directory_count = 0; + int m_file_count = 0; + + public IEnumerable Entries { get { return m_entries; } } + + public int Key { get { return m_key; } } + public int Size { get { return m_size; } } + public int TotalCount { get { return m_entries.Count; } } + public int FolderCount { get { return m_directory_count; } } + public int FileCount { get { return m_file_count; } } + + public Indexer (IEnumerable source_list, NpaOptions options) + { + m_entries = new List (source_list.Count()); + m_key = NpaOpener.GetArchiveKey (options.TitleId, options.Key1, options.Key2); + + foreach (var entry in source_list) + { + string name = entry.Name; + try + { + var dir = Path.GetDirectoryName (name); + int folder_id = 0; + if (!string.IsNullOrEmpty (dir)) + folder_id = AddDirectory (dir); + + bool compress = options.CompressContents; + if (compress) // don't compress images + compress = !FormatCatalog.Instance.LookupFileName (name).OfType().Any(); + var npa_entry = new NpaEntry + { + Name = name, + IsPacked = compress, + RawName = m_encoding.GetBytes (name), + FolderId = folder_id, + }; + ++m_file_count; + AddEntry (npa_entry); + } + catch (EncoderFallbackException X) + { + throw new InvalidFileName (name, arcStrings.MsgIllegalCharacters, X); + } + } + } + + void AddEntry (NpaEntry entry) + { + m_entries.Add (entry); + m_size += 4 + entry.RawName.Length + 17; + } + + Dictionary m_directory_map = new Dictionary(); + + int AddDirectory (string dir) + { + int folder_id = 0; + if (m_directory_map.TryGetValue (dir, out folder_id)) + return folder_id; + string path = ""; + foreach (var component in dir.Split (Path.DirectorySeparatorChar)) + { + path = Path.Combine (path, component); + if (m_directory_map.TryGetValue (path, out folder_id)) + continue; + folder_id = ++m_directory_count; + m_directory_map[path] = folder_id; + + var npa_entry = new NpaEntry + { + Name = path, + Type = "directory", + Offset = 0, + Size = 0, + UnpackedSize = 0, + IsPacked = false, + RawName = m_encoding.GetBytes (path), + FolderId = folder_id, + }; + AddEntry (npa_entry); + } + return folder_id; + } + } + + /// + /// Stream class for files stored in encrypted NPA archives. + /// + internal class EncryptedStream : Stream + { + private Stream m_stream; + private Lazy m_encrypted; + private int m_encrypted_length; + private bool m_read_mode; + private long m_base_pos; + + public override bool CanRead { get { return m_read_mode && m_stream.CanRead; } } + public override bool CanSeek { get { return m_stream.CanSeek; } } + public override bool CanWrite { get { return !m_read_mode && m_stream.CanWrite; } } + public override long Length { get { return m_stream.Length - m_base_pos; } } + public override long Position + { + get { return m_stream.Position - m_base_pos; } + set { m_stream.Position = m_base_pos + value; } + } + + delegate byte CryptFunc (int index, byte value); + CryptFunc Encrypt; + + public EncryptedStream (NpaArchive arc, NpaEntry entry) + { + m_read_mode = true; + m_encrypted_length = GetEncryptedLength (entry, arc.GameId); + if (m_encrypted_length > entry.Size) + m_encrypted_length = (int)entry.Size; + int key = NpaOpener.GetKeyFromEntry (entry, arc.GameId, arc.Key); + + m_stream = arc.File.CreateStream (entry.Offset, entry.Size); + m_encrypted = new Lazy (() => InitEncrypted (key, arc.GameId, arc.KeyTable)); + m_base_pos = m_stream.Position; + } + + public EncryptedStream (Stream output, NpaEntry entry, NpaTitleId game_id, int arc_key) + { + m_read_mode = false; + m_encrypted_length = GetEncryptedLength (entry, game_id); + int key = NpaOpener.GetKeyFromEntry (entry, game_id, arc_key); + + m_stream = output; + m_encrypted = new Lazy (() => new byte[m_encrypted_length]); + m_base_pos = m_stream.Position; + + byte[] decrypt_table = NpaOpener.GenerateKeyTable (game_id); + byte[] encrypt_table = new byte[256]; + for (int i = 0; i < 256; ++i) + encrypt_table[decrypt_table[i]] = (byte)i; + + if (NpaTitleId.LAMENTO == game_id) + { + Encrypt = (i, x) => encrypt_table[(x + key) & 0xff]; + } + else + { + Encrypt = (i, x) => encrypt_table[(x + key + i) & 0xff]; + } + } + + int GetEncryptedLength (NpaEntry entry, NpaTitleId game_id) + { + int length = 0x1000; + if (game_id != NpaTitleId.LAMENTO) + length += entry.RawName.Length; + return length; + } + + byte[] InitEncrypted (int key, NpaTitleId game_id, byte[] key_table) + { + var position = Position; + if (0 != position) + Position = 0; + byte[] buffer = new byte[m_encrypted_length]; + m_encrypted_length = m_stream.Read (buffer, 0, m_encrypted_length); + Position = position; + + if (game_id == NpaTitleId.LAMENTO) + { + for (int i = 0; i < m_encrypted_length; i++) + buffer[i] = (byte)(key_table[buffer[i]] - key); + } + else + { + for (int i = 0; i < m_encrypted_length; i++) + buffer[i] = (byte)(key_table[buffer[i]] - key - i); + } + return buffer; + } + + #region System.IO.Stream methods + public override void Flush() + { + m_stream.Flush(); + } + + public override long Seek (long offset, SeekOrigin origin) + { + if (SeekOrigin.Begin == origin) + offset += m_base_pos; + offset = m_stream.Seek (offset, origin); + return offset - m_base_pos; + } + + public override void SetLength (long length) + { + throw new NotSupportedException ("EncryptedStream.SetLength is not supported"); + } + + public override int Read (byte[] buffer, int offset, int count) + { + var position = Position; + if (position >= m_encrypted_length) + return m_stream.Read (buffer, offset, count); + int read = Math.Min (m_encrypted_length - (int)position, count); + Buffer.BlockCopy (m_encrypted.Value, (int)position, buffer, offset, read); + m_stream.Seek (read, SeekOrigin.Current); + if (read < count) + { + read += m_stream.Read (buffer, offset+read, count-read); + } + return read; + } + + public override int ReadByte () + { + var position = Position; + if (position >= m_encrypted_length) + return m_stream.ReadByte(); + m_stream.Seek (1, SeekOrigin.Current); + return m_encrypted.Value[(int)position]; + } + + public override void Write (byte[] buffer, int offset, int count) + { + var position = Position; + if (position < m_encrypted_length) + { + int limit = (int)position + Math.Min (m_encrypted_length - (int)position, count); + for (int i = (int)position; i < limit; ++i, ++offset, --count) + { + m_encrypted.Value[i] = Encrypt (i, buffer[offset]); + } + m_stream.Write (m_encrypted.Value, (int)position, limit-(int)position); + } + if (count > 0) + m_stream.Write (buffer, offset, count); + } + + public override void WriteByte (byte value) + { + var position = Position; + if (position < m_encrypted_length) + value = Encrypt ((int)position, value); + m_stream.WriteByte (value); + } + #endregion + + #region IDisposable Members + bool disposed = false; + protected override void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing && m_read_mode) + { + m_stream.Dispose(); + } + m_encrypted = null; + disposed = true; + base.Dispose (disposing); + } + } + #endregion + } +} diff --git a/ArcFormats/ArcNitro.cs b/ArcFormats/NitroPlus/ArcNitro.cs similarity index 100% rename from ArcFormats/ArcNitro.cs rename to ArcFormats/NitroPlus/ArcNitro.cs diff --git a/ArcFormats/ArcSteinsGate.cs b/ArcFormats/NitroPlus/ArcSteinsGate.cs similarity index 100% rename from ArcFormats/ArcSteinsGate.cs rename to ArcFormats/NitroPlus/ArcSteinsGate.cs diff --git a/ArcFormats/CreateNPAWidget.xaml b/ArcFormats/NitroPlus/CreateNPAWidget.xaml similarity index 100% rename from ArcFormats/CreateNPAWidget.xaml rename to ArcFormats/NitroPlus/CreateNPAWidget.xaml diff --git a/ArcFormats/CreateNPAWidget.xaml.cs b/ArcFormats/NitroPlus/CreateNPAWidget.xaml.cs similarity index 100% rename from ArcFormats/CreateNPAWidget.xaml.cs rename to ArcFormats/NitroPlus/CreateNPAWidget.xaml.cs diff --git a/ArcFormats/CreateSGWidget.xaml b/ArcFormats/NitroPlus/CreateSGWidget.xaml similarity index 100% rename from ArcFormats/CreateSGWidget.xaml rename to ArcFormats/NitroPlus/CreateSGWidget.xaml diff --git a/ArcFormats/CreateSGWidget.xaml.cs b/ArcFormats/NitroPlus/CreateSGWidget.xaml.cs similarity index 100% rename from ArcFormats/CreateSGWidget.xaml.cs rename to ArcFormats/NitroPlus/CreateSGWidget.xaml.cs diff --git a/ArcFormats/WidgetNPA.xaml b/ArcFormats/NitroPlus/WidgetNPA.xaml similarity index 98% rename from ArcFormats/WidgetNPA.xaml rename to ArcFormats/NitroPlus/WidgetNPA.xaml index d95a4d8f..e1710d00 100644 --- a/ArcFormats/WidgetNPA.xaml +++ b/ArcFormats/NitroPlus/WidgetNPA.xaml @@ -1,10 +1,10 @@ - - - + + + diff --git a/ArcFormats/WidgetNPA.xaml.cs b/ArcFormats/NitroPlus/WidgetNPA.xaml.cs similarity index 96% rename from ArcFormats/WidgetNPA.xaml.cs rename to ArcFormats/NitroPlus/WidgetNPA.xaml.cs index 8923e076..3ac974bb 100644 --- a/ArcFormats/WidgetNPA.xaml.cs +++ b/ArcFormats/NitroPlus/WidgetNPA.xaml.cs @@ -1,26 +1,26 @@ -using System.Windows; -using System.Windows.Controls; -using System.Linq; -using GameRes.Formats.Properties; -using GameRes.Formats.NitroPlus; - -namespace GameRes.Formats.GUI -{ - /// - /// Interaction logic for WidgetNPA.xaml - /// - public partial class WidgetNPA : Grid - { - public WidgetNPA () - { - var selected = Settings.Default.NPAScheme; - InitializeComponent(); - var sorted = NpaOpener.KnownSchemes.Skip (1).OrderBy (x => x); - Scheme.ItemsSource = NpaOpener.KnownSchemes.Take(1).Concat (sorted); - if (NpaTitleId.NotEncrypted == NpaOpener.GetTitleId (selected)) - Scheme.SelectedIndex = 0; - else - Scheme.SelectedValue = selected; - } - } -} +using System.Windows; +using System.Windows.Controls; +using System.Linq; +using GameRes.Formats.Properties; +using GameRes.Formats.NitroPlus; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetNPA.xaml + /// + public partial class WidgetNPA : Grid + { + public WidgetNPA () + { + var selected = Settings.Default.NPAScheme; + InitializeComponent(); + var sorted = NpaOpener.KnownSchemes.Skip (1).OrderBy (x => x); + Scheme.ItemsSource = NpaOpener.KnownSchemes.Take(1).Concat (sorted); + if (NpaTitleId.NotEncrypted == NpaOpener.GetTitleId (selected)) + Scheme.SelectedIndex = 0; + else + Scheme.SelectedValue = selected; + } + } +} diff --git a/ArcFormats/ArcGameDat.cs b/ArcFormats/Pajamas/ArcGameDat.cs similarity index 100% rename from ArcFormats/ArcGameDat.cs rename to ArcFormats/Pajamas/ArcGameDat.cs diff --git a/ArcFormats/ImageEPA.cs b/ArcFormats/Pajamas/ImageEPA.cs similarity index 100% rename from ArcFormats/ImageEPA.cs rename to ArcFormats/Pajamas/ImageEPA.cs diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index 289b6623..b60193ed 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.9.95")] -[assembly: AssemblyFileVersion ("1.1.9.95")] +[assembly: AssemblyVersion ("1.1.9.399")] +[assembly: AssemblyFileVersion ("1.1.9.399")] diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 9969900b..ccb14470 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -61,9 +61,9 @@ namespace GameRes.Formats.Properties { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - public global::GameRes.Formats.IntEncryptionInfo INTEncryption { + public global::GameRes.Formats.CatSystem.IntEncryptionInfo INTEncryption { get { - return ((global::GameRes.Formats.IntEncryptionInfo)(this["INTEncryption"])); + return ((global::GameRes.Formats.CatSystem.IntEncryptionInfo)(this["INTEncryption"])); } set { this["INTEncryption"] = value; diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 72014501..09608314 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -11,7 +11,7 @@ 4294967295 - + @@ -102,4 +102,4 @@ - \ No newline at end of file + diff --git a/ArcFormats/ArcRPA.cs b/ArcFormats/RenPy/ArcRPA.cs similarity index 100% rename from ArcFormats/ArcRPA.cs rename to ArcFormats/RenPy/ArcRPA.cs diff --git a/ArcFormats/CreateRPAWidget.xaml b/ArcFormats/RenPy/CreateRPAWidget.xaml similarity index 100% rename from ArcFormats/CreateRPAWidget.xaml rename to ArcFormats/RenPy/CreateRPAWidget.xaml diff --git a/ArcFormats/CreateRPAWidget.xaml.cs b/ArcFormats/RenPy/CreateRPAWidget.xaml.cs similarity index 100% rename from ArcFormats/CreateRPAWidget.xaml.cs rename to ArcFormats/RenPy/CreateRPAWidget.xaml.cs diff --git a/ArcFormats/ArcPAC.cs b/ArcFormats/RiddleSoft/ArcPAC.cs similarity index 100% rename from ArcFormats/ArcPAC.cs rename to ArcFormats/RiddleSoft/ArcPAC.cs diff --git a/ArcFormats/ImageGCP.cs b/ArcFormats/RiddleSoft/ImageGCP.cs similarity index 100% rename from ArcFormats/ImageGCP.cs rename to ArcFormats/RiddleSoft/ImageGCP.cs diff --git a/ArcFormats/AudioPMW.cs b/ArcFormats/ScenePlayer/AudioPMW.cs similarity index 100% rename from ArcFormats/AudioPMW.cs rename to ArcFormats/ScenePlayer/AudioPMW.cs diff --git a/ArcFormats/ImagePMP.cs b/ArcFormats/ScenePlayer/ImagePMP.cs similarity index 100% rename from ArcFormats/ImagePMP.cs rename to ArcFormats/ScenePlayer/ImagePMP.cs diff --git a/ArcFormats/ArcKCAP.cs b/ArcFormats/Selene/ArcKCAP.cs similarity index 100% rename from ArcFormats/ArcKCAP.cs rename to ArcFormats/Selene/ArcKCAP.cs diff --git a/ArcFormats/WidgetKCAP.xaml b/ArcFormats/Selene/WidgetKCAP.xaml similarity index 100% rename from ArcFormats/WidgetKCAP.xaml rename to ArcFormats/Selene/WidgetKCAP.xaml diff --git a/ArcFormats/WidgetKCAP.xaml.cs b/ArcFormats/Selene/WidgetKCAP.xaml.cs similarity index 100% rename from ArcFormats/WidgetKCAP.xaml.cs rename to ArcFormats/Selene/WidgetKCAP.xaml.cs diff --git a/ArcFormats/ArcS25.cs b/ArcFormats/ShiinaRio/ArcS25.cs similarity index 100% rename from ArcFormats/ArcS25.cs rename to ArcFormats/ShiinaRio/ArcS25.cs diff --git a/ArcFormats/ArcWARC.cs b/ArcFormats/ShiinaRio/ArcWARC.cs similarity index 100% rename from ArcFormats/ArcWARC.cs rename to ArcFormats/ShiinaRio/ArcWARC.cs diff --git a/ArcFormats/AudioOGV.cs b/ArcFormats/ShiinaRio/AudioOGV.cs similarity index 100% rename from ArcFormats/AudioOGV.cs rename to ArcFormats/ShiinaRio/AudioOGV.cs diff --git a/ArcFormats/AudioPAD.cs b/ArcFormats/ShiinaRio/AudioPAD.cs similarity index 100% rename from ArcFormats/AudioPAD.cs rename to ArcFormats/ShiinaRio/AudioPAD.cs diff --git a/ArcFormats/ImageMI4.cs b/ArcFormats/ShiinaRio/ImageMI4.cs similarity index 100% rename from ArcFormats/ImageMI4.cs rename to ArcFormats/ShiinaRio/ImageMI4.cs diff --git a/ArcFormats/ImageS25.cs b/ArcFormats/ShiinaRio/ImageS25.cs similarity index 100% rename from ArcFormats/ImageS25.cs rename to ArcFormats/ShiinaRio/ImageS25.cs diff --git a/ArcFormats/WidgetWARC.xaml b/ArcFormats/ShiinaRio/WidgetWARC.xaml similarity index 100% rename from ArcFormats/WidgetWARC.xaml rename to ArcFormats/ShiinaRio/WidgetWARC.xaml diff --git a/ArcFormats/WidgetWARC.xaml.cs b/ArcFormats/ShiinaRio/WidgetWARC.xaml.cs similarity index 100% rename from ArcFormats/WidgetWARC.xaml.cs rename to ArcFormats/ShiinaRio/WidgetWARC.xaml.cs diff --git a/ArcFormats/ArcIFL.cs b/ArcFormats/Silky/ArcIFL.cs similarity index 100% rename from ArcFormats/ArcIFL.cs rename to ArcFormats/Silky/ArcIFL.cs diff --git a/ArcFormats/ArcMFG.cs b/ArcFormats/Silky/ArcMFG.cs similarity index 100% rename from ArcFormats/ArcMFG.cs rename to ArcFormats/Silky/ArcMFG.cs diff --git a/ArcFormats/ImageGRD.cs b/ArcFormats/Silky/ImageGRD.cs similarity index 100% rename from ArcFormats/ImageGRD.cs rename to ArcFormats/Silky/ImageGRD.cs diff --git a/ArcFormats/ImageIGF.cs b/ArcFormats/Silky/ImageIGF.cs similarity index 100% rename from ArcFormats/ImageIGF.cs rename to ArcFormats/Silky/ImageIGF.cs diff --git a/ArcFormats/ImageMFG.cs b/ArcFormats/Silky/ImageMFG.cs similarity index 100% rename from ArcFormats/ImageMFG.cs rename to ArcFormats/Silky/ImageMFG.cs diff --git a/ArcFormats/ArcTactics.cs b/ArcFormats/Tactics/ArcTactics.cs similarity index 100% rename from ArcFormats/ArcTactics.cs rename to ArcFormats/Tactics/ArcTactics.cs diff --git a/ArcFormats/ImageTGF.cs b/ArcFormats/Tactics/ImageTGF.cs similarity index 100% rename from ArcFormats/ImageTGF.cs rename to ArcFormats/Tactics/ImageTGF.cs diff --git a/ArcFormats/ArcSUD.cs b/ArcFormats/Triangle/ArcSUD.cs similarity index 100% rename from ArcFormats/ArcSUD.cs rename to ArcFormats/Triangle/ArcSUD.cs diff --git a/ArcFormats/ImageIAF.cs b/ArcFormats/Triangle/ImageIAF.cs similarity index 100% rename from ArcFormats/ImageIAF.cs rename to ArcFormats/Triangle/ImageIAF.cs diff --git a/ArcFormats/ArcPK.cs b/ArcFormats/UMeSoft/ArcPK.cs similarity index 100% rename from ArcFormats/ArcPK.cs rename to ArcFormats/UMeSoft/ArcPK.cs diff --git a/ArcFormats/ImageGRX.cs b/ArcFormats/UMeSoft/ImageGRX.cs similarity index 100% rename from ArcFormats/ImageGRX.cs rename to ArcFormats/UMeSoft/ImageGRX.cs diff --git a/ArcFormats/ArcWBP.cs b/ArcFormats/WildBug/ArcWBP.cs similarity index 100% rename from ArcFormats/ArcWBP.cs rename to ArcFormats/WildBug/ArcWBP.cs diff --git a/ArcFormats/AudioWPN.cs b/ArcFormats/WildBug/AudioWPN.cs similarity index 100% rename from ArcFormats/AudioWPN.cs rename to ArcFormats/WildBug/AudioWPN.cs diff --git a/ArcFormats/AudioWWA.cs b/ArcFormats/WildBug/AudioWWA.cs similarity index 100% rename from ArcFormats/AudioWWA.cs rename to ArcFormats/WildBug/AudioWWA.cs diff --git a/ArcFormats/ImageWBM.cs b/ArcFormats/WildBug/ImageWBM.cs similarity index 100% rename from ArcFormats/ImageWBM.cs rename to ArcFormats/WildBug/ImageWBM.cs diff --git a/ArcFormats/ArcWILL.cs b/ArcFormats/Will/ArcWILL.cs similarity index 100% rename from ArcFormats/ArcWILL.cs rename to ArcFormats/Will/ArcWILL.cs diff --git a/ArcFormats/CreateARCWidget.xaml b/ArcFormats/Will/CreateARCWidget.xaml similarity index 100% rename from ArcFormats/CreateARCWidget.xaml rename to ArcFormats/Will/CreateARCWidget.xaml diff --git a/ArcFormats/CreateARCWidget.xaml.cs b/ArcFormats/Will/CreateARCWidget.xaml.cs similarity index 100% rename from ArcFormats/CreateARCWidget.xaml.cs rename to ArcFormats/Will/CreateARCWidget.xaml.cs diff --git a/ArcFormats/ImageWIP.cs b/ArcFormats/Will/ImageWIP.cs similarity index 100% rename from ArcFormats/ImageWIP.cs rename to ArcFormats/Will/ImageWIP.cs diff --git a/ArcFormats/ArcWAG.cs b/ArcFormats/Xuse/ArcWAG.cs similarity index 100% rename from ArcFormats/ArcWAG.cs rename to ArcFormats/Xuse/ArcWAG.cs diff --git a/ArcFormats/ArcXuse.cs b/ArcFormats/Xuse/ArcXuse.cs similarity index 100% rename from ArcFormats/ArcXuse.cs rename to ArcFormats/Xuse/ArcXuse.cs diff --git a/ArcFormats/ArcYPF.cs b/ArcFormats/YuRis/ArcYPF.cs similarity index 97% rename from ArcFormats/ArcYPF.cs rename to ArcFormats/YuRis/ArcYPF.cs index cff26c65..02c4fdf5 100644 --- a/ArcFormats/ArcYPF.cs +++ b/ArcFormats/YuRis/ArcYPF.cs @@ -1,367 +1,367 @@ -//! \file ArcYPF.cs -//! \date Mon Jul 14 14:40:06 2014 -//! \brief YPF resource 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.IO; -using System.Text; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using GameRes.Compression; -using GameRes.Formats.Strings; -using GameRes.Formats.Properties; -using GameRes.Utility; - -namespace GameRes.Formats.YuRis -{ - public class YpfOptions : ResourceOptions - { - public uint Key { get; set; } - public uint Version { get; set; } - } - - [Export(typeof(ArchiveFormat))] - public class YpfOpener : ArchiveFormat - { - public override string Tag { get { return "YPF"; } } - public override string Description { get { return arcStrings.YPFDescription; } } - public override uint Signature { get { return 0x00465059; } } - public override bool IsHierarchic { get { return true; } } - public override bool CanCreate { get { return true; } } - - private const uint DefaultKey = 0xffffffff; - - public override ArcFile TryOpen (ArcView file) - { - uint version = file.View.ReadUInt32 (4); - uint count = file.View.ReadUInt32 (8); - uint dir_size = file.View.ReadUInt32 (12); - if (dir_size < count * 0x17 || count > 0xfffff) - return null; - if (dir_size > file.View.Reserve (0x20, dir_size)) - return null; - var parser = new Parser (file, version, count, dir_size); - - uint key = QueryEncryptionKey(); - var dir = parser.ScanDir (key); - if (0 == dir.Count) - return null; - - return new ArcFile (file, this, dir); - } - - public override Stream OpenEntry (ArcFile arc, Entry entry) - { - var input = arc.File.CreateStream (entry.Offset, entry.Size); - var packed_entry = entry as PackedEntry; - if (null == packed_entry || !packed_entry.IsPacked) - return input; - else - return new ZLibStream (input, CompressionMode.Decompress); - } - - public override ResourceOptions GetDefaultOptions () - { - return new YpfOptions - { - Key = Settings.Default.YPFKey, - Version = Settings.Default.YPFVersion, - }; - } - - public override object GetAccessWidget () - { - return new GUI.WidgetYPF(); - } - - public override object GetCreationWidget () - { - return new GUI.CreateYPFWidget(); - } - - uint QueryEncryptionKey () - { - var options = Query (arcStrings.YPFNotice); - return options.Key; - } - - internal class YpfEntry : PackedEntry - { - public byte[] IndexName; - public uint NameHash; - public byte FileType; - public uint CheckSum; - } - - delegate uint ChecksumFunc (byte[] data); - - public override void Create (Stream output, IEnumerable list, ResourceOptions options, - EntryCallback callback) - { - var ypf_options = GetOptions (options); - if (null == ypf_options) - throw new ArgumentException ("Invalid archive creation options", "options"); - if (ypf_options.Key > 0xff) - throw new InvalidEncryptionScheme (arcStrings.MsgCreationKeyRequired); - if (0 == ypf_options.Version) - throw new InvalidFormatException (arcStrings.MsgInvalidVersion); - - int callback_count = 0; - var encoding = Encodings.cp932.WithFatalFallback(); - - ChecksumFunc Checksum = data => Crc32.Compute (data, 0, data.Length); - - uint data_offset = 0x20; - var file_table = new List(); - foreach (var entry in list) - { - try - { - string file_name = entry.Name; - byte[] name_buf = encoding.GetBytes (file_name); - if (name_buf.Length > 0xff) - throw new InvalidFileName (entry.Name, arcStrings.MsgFileNameTooLong); - uint hash = Checksum (name_buf); - byte file_type = GetFileType (ypf_options.Version, file_name); - - for (int i = 0; i < name_buf.Length; ++i) - name_buf[i] = (byte)(name_buf[i] ^ ypf_options.Key); - - file_table.Add (new YpfEntry { - Name = file_name, - IndexName = name_buf, - NameHash = hash, - FileType = file_type, - IsPacked = 0 == file_type, - }); - data_offset += (uint)(0x17 + name_buf.Length); - } - catch (EncoderFallbackException X) - { - throw new InvalidFileName (entry.Name, arcStrings.MsgIllegalCharacters, X); - } - } - file_table.Sort ((a, b) => a.NameHash.CompareTo (b.NameHash)); - - output.Position = data_offset; - uint current_offset = data_offset; - foreach (var entry in file_table) - { - if (null != callback) - callback (callback_count++, entry, arcStrings.MsgAddingFile); - - entry.Offset = current_offset; - using (var input = File.OpenRead (entry.Name)) - { - var file_size = input.Length; - if (file_size > uint.MaxValue || current_offset + file_size > uint.MaxValue) - throw new FileSizeException(); - entry.UnpackedSize = (uint)file_size; - using (var checked_stream = new CheckedStream (output, new Adler32())) - { - if (entry.IsPacked) - { - var start = output.Position; - using (var zstream = new ZLibStream (checked_stream, CompressionMode.Compress, - CompressionLevel.Level9, true)) - { - input.CopyTo (zstream); - } - entry.Size = (uint)(output.Position - start); - } - else - { - input.CopyTo (checked_stream); - entry.Size = entry.UnpackedSize; - } - checked_stream.Flush(); - entry.CheckSum = checked_stream.CheckSumValue; - current_offset += entry.Size; - } - } - } - - if (null != callback) - callback (callback_count++, null, arcStrings.MsgWritingIndex); - - output.Position = 0; - using (var writer = new BinaryWriter (output, encoding, true)) - { - writer.Write (Signature); - writer.Write (ypf_options.Version); - writer.Write (file_table.Count); - writer.Write (data_offset); - writer.BaseStream.Seek (0x20, SeekOrigin.Begin); - foreach (var entry in file_table) - { - writer.Write (entry.NameHash); - byte name_len = (byte)~Parser.Decrypt (ypf_options.Version, (byte)entry.IndexName.Length); - writer.Write (name_len); - writer.Write (entry.IndexName); - writer.Write (entry.FileType); - writer.Write (entry.IsPacked); - writer.Write (entry.UnpackedSize); - writer.Write (entry.Size); - writer.Write ((uint)entry.Offset); - writer.Write (entry.CheckSum); - } - } - } - - static byte GetFileType (uint version, string name) - { - // 0x0F7: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-avi, 6-wav, 7-ogg, 8-psd - // 0x122, 0x12C, 0x196: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd - string ext = Path.GetExtension (name).TrimStart ('.').ToLower(); - if ("ybn" == ext) return 0; - if ("bmp" == ext) return 1; - if ("png" == ext) return 2; - if ("jpg" == ext || "jpeg" == ext) return 3; - if ("gif" == ext) return 4; - if ("avi" == ext && 0xf7 == version) return 5; - byte type = 0; - if ("wav" == ext) type = 5; - else if ("ogg" == ext) type = 6; - else if ("psd" == ext) type = 7; - if (0xf7 == version && 0 != type) - ++type; - return type; - } - - private class Parser - { - ArcView m_file; - uint m_version; - uint m_count; - uint m_dir_size; - - public Parser (ArcView file, uint version, uint count, uint dir_size) - { - m_file = file; - m_count = count; - m_dir_size = dir_size; - m_version = version; - } - // 4-name_checksum, 1-name_count, *-name, 1-file_type - // 1-pack_flag, 4-size, 4-packed_size, 4-offset, 4-packed_adler32 - - public List ScanDir (uint key) - { - uint dir_offset = 0x20; - uint dir_remaining = m_dir_size; - var dir = new List ((int)m_count); - for (uint num = 0; num < m_count; ++num) - { - if (dir_remaining < 0x17) - break; - dir_remaining -= 0x17; - - uint name_size = Decrypt (m_version, (byte)(m_file.View.ReadByte (dir_offset+4) ^ 0xff)); - if (name_size > dir_remaining) - break; - dir_remaining -= name_size; - dir_offset += 5; - if (0 == name_size) - break; - if (0xffffffff == key) - { - if (name_size < 4) - break; - // assume filename contains '.' and 3-characters extension. - key = (uint)(m_file.View.ReadByte (dir_offset+name_size-4) ^ 0x2e); - } - byte[] raw_name = new byte[name_size]; - for (int i = 0; i < name_size; ++i) - { - raw_name[i] = (byte)(m_file.View.ReadByte (dir_offset) ^ key); - ++dir_offset; - } - string name = Encodings.cp932.GetString (raw_name); - // 0x0F7: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-avi, 6-wav, 7-ogg, 8-psd - // 0x122, 0x12C, 0x196: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd - int type_id = m_file.View.ReadByte (dir_offset); - string type = ""; - switch (type_id) - { - case 0: - type = "script"; - break; - case 1: case 2: case 3: case 4: - type = "image"; - break; - case 5: - type = 0xf7 == m_version ? "video" : "audio"; - break; - case 6: - case 7: - type = "audio"; - break; - } - var entry = new PackedEntry { Name = name, Type = type }; - entry.IsPacked = 1 == m_file.View.ReadByte (dir_offset+1); - entry.UnpackedSize = m_file.View.ReadUInt32 (dir_offset+2); - entry.Size = m_file.View.ReadUInt32 (dir_offset+6); - entry.Offset = m_file.View.ReadUInt32 (dir_offset+10); - if (entry.CheckPlacement (m_file.MaxOffset)) - dir.Add (entry); - dir_offset += 0x12; - } - return dir; - } - - static readonly byte[] s_crypt_table = { - 0x03,0x48,0x06,0x35, // 0x122, 0x196 - 0x0C,0x10,0x11,0x19,0x1C,0x1E, // 0x0F7 - 0x09,0x0B,0x0D,0x13,0x15,0x1B, // 0x12C - 0x20,0x23,0x26,0x29, - 0x2C,0x2F,0x2E,0x32, - }; - // 0xFF 0x0F7 "Four-Leaf" adler32 - // 0x34 0x122 "Neko Koi!" crc32 - // 0x28 0x12C "Suzukaze no Melt" (no recovery - 00 00 00 00) - // 0xFF 0x196 "Mamono Musume-tachi to no Rakuen ~Slime & Scylla~" - - static public byte Decrypt (uint version, byte value) - { - int pos = 4; - if (version >= 0x100) - { - if (version >= 0x12c && version < 0x196) - pos = 10; - else - pos = 0; - } - pos = Array.IndexOf (s_crypt_table, value, pos); - if (-1 == pos) - return value; - if (0 != (pos & 1)) - return s_crypt_table[pos-1]; - else - return s_crypt_table[pos+1]; - } - } - } -} +//! \file ArcYPF.cs +//! \date Mon Jul 14 14:40:06 2014 +//! \brief YPF resource 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.IO; +using System.Text; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Compression; +using GameRes.Formats.Strings; +using GameRes.Formats.Properties; +using GameRes.Utility; + +namespace GameRes.Formats.YuRis +{ + public class YpfOptions : ResourceOptions + { + public uint Key { get; set; } + public uint Version { get; set; } + } + + [Export(typeof(ArchiveFormat))] + public class YpfOpener : ArchiveFormat + { + public override string Tag { get { return "YPF"; } } + public override string Description { get { return arcStrings.YPFDescription; } } + public override uint Signature { get { return 0x00465059; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return true; } } + + private const uint DefaultKey = 0xffffffff; + + public override ArcFile TryOpen (ArcView file) + { + uint version = file.View.ReadUInt32 (4); + uint count = file.View.ReadUInt32 (8); + uint dir_size = file.View.ReadUInt32 (12); + if (dir_size < count * 0x17 || count > 0xfffff) + return null; + if (dir_size > file.View.Reserve (0x20, dir_size)) + return null; + var parser = new Parser (file, version, count, dir_size); + + uint key = QueryEncryptionKey(); + var dir = parser.ScanDir (key); + if (0 == dir.Count) + return null; + + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var packed_entry = entry as PackedEntry; + if (null == packed_entry || !packed_entry.IsPacked) + return input; + else + return new ZLibStream (input, CompressionMode.Decompress); + } + + public override ResourceOptions GetDefaultOptions () + { + return new YpfOptions + { + Key = Settings.Default.YPFKey, + Version = Settings.Default.YPFVersion, + }; + } + + public override object GetAccessWidget () + { + return new GUI.WidgetYPF(); + } + + public override object GetCreationWidget () + { + return new GUI.CreateYPFWidget(); + } + + uint QueryEncryptionKey () + { + var options = Query (arcStrings.YPFNotice); + return options.Key; + } + + internal class YpfEntry : PackedEntry + { + public byte[] IndexName; + public uint NameHash; + public byte FileType; + public uint CheckSum; + } + + delegate uint ChecksumFunc (byte[] data); + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + var ypf_options = GetOptions (options); + if (null == ypf_options) + throw new ArgumentException ("Invalid archive creation options", "options"); + if (ypf_options.Key > 0xff) + throw new InvalidEncryptionScheme (arcStrings.MsgCreationKeyRequired); + if (0 == ypf_options.Version) + throw new InvalidFormatException (arcStrings.MsgInvalidVersion); + + int callback_count = 0; + var encoding = Encodings.cp932.WithFatalFallback(); + + ChecksumFunc Checksum = data => Crc32.Compute (data, 0, data.Length); + + uint data_offset = 0x20; + var file_table = new List(); + foreach (var entry in list) + { + try + { + string file_name = entry.Name; + byte[] name_buf = encoding.GetBytes (file_name); + if (name_buf.Length > 0xff) + throw new InvalidFileName (entry.Name, arcStrings.MsgFileNameTooLong); + uint hash = Checksum (name_buf); + byte file_type = GetFileType (ypf_options.Version, file_name); + + for (int i = 0; i < name_buf.Length; ++i) + name_buf[i] = (byte)(name_buf[i] ^ ypf_options.Key); + + file_table.Add (new YpfEntry { + Name = file_name, + IndexName = name_buf, + NameHash = hash, + FileType = file_type, + IsPacked = 0 == file_type, + }); + data_offset += (uint)(0x17 + name_buf.Length); + } + catch (EncoderFallbackException X) + { + throw new InvalidFileName (entry.Name, arcStrings.MsgIllegalCharacters, X); + } + } + file_table.Sort ((a, b) => a.NameHash.CompareTo (b.NameHash)); + + output.Position = data_offset; + uint current_offset = data_offset; + foreach (var entry in file_table) + { + if (null != callback) + callback (callback_count++, entry, arcStrings.MsgAddingFile); + + entry.Offset = current_offset; + using (var input = File.OpenRead (entry.Name)) + { + var file_size = input.Length; + if (file_size > uint.MaxValue || current_offset + file_size > uint.MaxValue) + throw new FileSizeException(); + entry.UnpackedSize = (uint)file_size; + using (var checked_stream = new CheckedStream (output, new Adler32())) + { + if (entry.IsPacked) + { + var start = output.Position; + using (var zstream = new ZLibStream (checked_stream, CompressionMode.Compress, + CompressionLevel.Level9, true)) + { + input.CopyTo (zstream); + } + entry.Size = (uint)(output.Position - start); + } + else + { + input.CopyTo (checked_stream); + entry.Size = entry.UnpackedSize; + } + checked_stream.Flush(); + entry.CheckSum = checked_stream.CheckSumValue; + current_offset += entry.Size; + } + } + } + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + + output.Position = 0; + using (var writer = new BinaryWriter (output, encoding, true)) + { + writer.Write (Signature); + writer.Write (ypf_options.Version); + writer.Write (file_table.Count); + writer.Write (data_offset); + writer.BaseStream.Seek (0x20, SeekOrigin.Begin); + foreach (var entry in file_table) + { + writer.Write (entry.NameHash); + byte name_len = (byte)~Parser.Decrypt (ypf_options.Version, (byte)entry.IndexName.Length); + writer.Write (name_len); + writer.Write (entry.IndexName); + writer.Write (entry.FileType); + writer.Write (entry.IsPacked); + writer.Write (entry.UnpackedSize); + writer.Write (entry.Size); + writer.Write ((uint)entry.Offset); + writer.Write (entry.CheckSum); + } + } + } + + static byte GetFileType (uint version, string name) + { + // 0x0F7: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-avi, 6-wav, 7-ogg, 8-psd + // 0x122, 0x12C, 0x196: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd + string ext = Path.GetExtension (name).TrimStart ('.').ToLower(); + if ("ybn" == ext) return 0; + if ("bmp" == ext) return 1; + if ("png" == ext) return 2; + if ("jpg" == ext || "jpeg" == ext) return 3; + if ("gif" == ext) return 4; + if ("avi" == ext && 0xf7 == version) return 5; + byte type = 0; + if ("wav" == ext) type = 5; + else if ("ogg" == ext) type = 6; + else if ("psd" == ext) type = 7; + if (0xf7 == version && 0 != type) + ++type; + return type; + } + + private class Parser + { + ArcView m_file; + uint m_version; + uint m_count; + uint m_dir_size; + + public Parser (ArcView file, uint version, uint count, uint dir_size) + { + m_file = file; + m_count = count; + m_dir_size = dir_size; + m_version = version; + } + // 4-name_checksum, 1-name_count, *-name, 1-file_type + // 1-pack_flag, 4-size, 4-packed_size, 4-offset, 4-packed_adler32 + + public List ScanDir (uint key) + { + uint dir_offset = 0x20; + uint dir_remaining = m_dir_size; + var dir = new List ((int)m_count); + for (uint num = 0; num < m_count; ++num) + { + if (dir_remaining < 0x17) + break; + dir_remaining -= 0x17; + + uint name_size = Decrypt (m_version, (byte)(m_file.View.ReadByte (dir_offset+4) ^ 0xff)); + if (name_size > dir_remaining) + break; + dir_remaining -= name_size; + dir_offset += 5; + if (0 == name_size) + break; + if (0xffffffff == key) + { + if (name_size < 4) + break; + // assume filename contains '.' and 3-characters extension. + key = (uint)(m_file.View.ReadByte (dir_offset+name_size-4) ^ 0x2e); + } + byte[] raw_name = new byte[name_size]; + for (int i = 0; i < name_size; ++i) + { + raw_name[i] = (byte)(m_file.View.ReadByte (dir_offset) ^ key); + ++dir_offset; + } + string name = Encodings.cp932.GetString (raw_name); + // 0x0F7: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-avi, 6-wav, 7-ogg, 8-psd + // 0x122, 0x12C, 0x196: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd + int type_id = m_file.View.ReadByte (dir_offset); + string type = ""; + switch (type_id) + { + case 0: + type = "script"; + break; + case 1: case 2: case 3: case 4: + type = "image"; + break; + case 5: + type = 0xf7 == m_version ? "video" : "audio"; + break; + case 6: + case 7: + type = "audio"; + break; + } + var entry = new PackedEntry { Name = name, Type = type }; + entry.IsPacked = 1 == m_file.View.ReadByte (dir_offset+1); + entry.UnpackedSize = m_file.View.ReadUInt32 (dir_offset+2); + entry.Size = m_file.View.ReadUInt32 (dir_offset+6); + entry.Offset = m_file.View.ReadUInt32 (dir_offset+10); + if (entry.CheckPlacement (m_file.MaxOffset)) + dir.Add (entry); + dir_offset += 0x12; + } + return dir; + } + + static readonly byte[] s_crypt_table = { + 0x03,0x48,0x06,0x35, // 0x122, 0x196 + 0x0C,0x10,0x11,0x19,0x1C,0x1E, // 0x0F7 + 0x09,0x0B,0x0D,0x13,0x15,0x1B, // 0x12C + 0x20,0x23,0x26,0x29, + 0x2C,0x2F,0x2E,0x32, + }; + // 0xFF 0x0F7 "Four-Leaf" adler32 + // 0x34 0x122 "Neko Koi!" crc32 + // 0x28 0x12C "Suzukaze no Melt" (no recovery - 00 00 00 00) + // 0xFF 0x196 "Mamono Musume-tachi to no Rakuen ~Slime & Scylla~" + + static public byte Decrypt (uint version, byte value) + { + int pos = 4; + if (version >= 0x100) + { + if (version >= 0x12c && version < 0x196) + pos = 10; + else + pos = 0; + } + pos = Array.IndexOf (s_crypt_table, value, pos); + if (-1 == pos) + return value; + if (0 != (pos & 1)) + return s_crypt_table[pos-1]; + else + return s_crypt_table[pos+1]; + } + } + } +} diff --git a/ArcFormats/CreateYPFWidget.xaml b/ArcFormats/YuRis/CreateYPFWidget.xaml similarity index 100% rename from ArcFormats/CreateYPFWidget.xaml rename to ArcFormats/YuRis/CreateYPFWidget.xaml diff --git a/ArcFormats/CreateYPFWidget.xaml.cs b/ArcFormats/YuRis/CreateYPFWidget.xaml.cs similarity index 100% rename from ArcFormats/CreateYPFWidget.xaml.cs rename to ArcFormats/YuRis/CreateYPFWidget.xaml.cs diff --git a/ArcFormats/WidgetYPF.xaml b/ArcFormats/YuRis/WidgetYPF.xaml similarity index 98% rename from ArcFormats/WidgetYPF.xaml rename to ArcFormats/YuRis/WidgetYPF.xaml index aef53417..5df5bedb 100644 --- a/ArcFormats/WidgetYPF.xaml +++ b/ArcFormats/YuRis/WidgetYPF.xaml @@ -1,18 +1,18 @@ - - - - - - - - - + + + + + + + + + diff --git a/ArcFormats/WidgetYPF.xaml.cs b/ArcFormats/YuRis/WidgetYPF.xaml.cs similarity index 95% rename from ArcFormats/WidgetYPF.xaml.cs rename to ArcFormats/YuRis/WidgetYPF.xaml.cs index bad6d307..81caca8a 100644 --- a/ArcFormats/WidgetYPF.xaml.cs +++ b/ArcFormats/YuRis/WidgetYPF.xaml.cs @@ -1,26 +1,26 @@ -using System.Windows; -using System.Windows.Controls; -using GameRes.Formats.Properties; - -namespace GameRes.Formats.GUI -{ - /// - /// Interaction logic for WidgetYPF.xaml - /// - public partial class WidgetYPF : Grid - { - public WidgetYPF () - { - InitializeComponent(); - } - - public uint? GetKey () - { - uint key; - if (uint.TryParse (this.Passkey.Text, out key) && key < 0x100) - return key; - else - return null; - } - } -} +using System.Windows; +using System.Windows.Controls; +using GameRes.Formats.Properties; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetYPF.xaml + /// + public partial class WidgetYPF : Grid + { + public WidgetYPF () + { + InitializeComponent(); + } + + public uint? GetKey () + { + uint key; + if (uint.TryParse (this.Passkey.Text, out key) && key < 0x100) + return key; + else + return null; + } + } +} diff --git a/ArcFormats/ArcYKC.cs b/ArcFormats/Yuka/ArcYKC.cs similarity index 100% rename from ArcFormats/ArcYKC.cs rename to ArcFormats/Yuka/ArcYKC.cs diff --git a/ArcFormats/ImageYKG.cs b/ArcFormats/Yuka/ImageYKG.cs similarity index 100% rename from ArcFormats/ImageYKG.cs rename to ArcFormats/Yuka/ImageYKG.cs diff --git a/ArcFormats/ArcAi5Win.cs b/ArcFormats/elf/ArcAi5Win.cs similarity index 100% rename from ArcFormats/ArcAi5Win.cs rename to ArcFormats/elf/ArcAi5Win.cs diff --git a/ArcFormats/ArcHED.cs b/ArcFormats/elf/ArcHED.cs similarity index 100% rename from ArcFormats/ArcHED.cs rename to ArcFormats/elf/ArcHED.cs diff --git a/ArcFormats/ImageGCC.cs b/ArcFormats/elf/ImageGCC.cs similarity index 100% rename from ArcFormats/ImageGCC.cs rename to ArcFormats/elf/ImageGCC.cs diff --git a/ArcFormats/ImageHIZ.cs b/ArcFormats/elf/ImageHIZ.cs similarity index 100% rename from ArcFormats/ImageHIZ.cs rename to ArcFormats/elf/ImageHIZ.cs