diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 6294bfd6..686f26d5 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -63,6 +63,7 @@ ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + ..\packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll @@ -92,6 +93,15 @@ ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + + ..\packages\System.Memory.4.5.4\lib\netstandard1.1\System.Memory.dll + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll @@ -120,6 +130,8 @@ + + @@ -226,6 +238,7 @@ + @@ -324,6 +337,7 @@ + @@ -336,6 +350,7 @@ + @@ -652,6 +667,7 @@ + @@ -792,12 +808,14 @@ + + diff --git a/ArcFormats/AudioOGG.cs b/ArcFormats/AudioOGG.cs index 390aef1b..866598c9 100644 --- a/ArcFormats/AudioOGG.cs +++ b/ArcFormats/AudioOGG.cs @@ -40,13 +40,13 @@ namespace GameRes.Formats { get { - return (long)(m_reader.DecodedTime.TotalSeconds * m_reader.SampleRate * m_reader.Channels * sizeof(float)); + return (long)(m_reader.TimePosition.TotalSeconds * m_reader.SampleRate * m_reader.Channels * sizeof(float)); } set { if (value < 0 || value > Length) throw new ArgumentOutOfRangeException("value"); - m_reader.DecodedTime = TimeSpan.FromSeconds((double)value / m_reader.SampleRate / m_reader.Channels / sizeof(float)); + m_reader.TimePosition = TimeSpan.FromSeconds((double)value / m_reader.SampleRate / m_reader.Channels / sizeof(float)); } } @@ -75,7 +75,7 @@ namespace GameRes.Formats public override void Reset () { - m_reader.DecodedTime = TimeSpan.FromSeconds (0); + m_reader.TimePosition = TimeSpan.FromSeconds (0); } // This buffer can be static because it can only be used by 1 instance per thread diff --git a/ArcFormats/Cyberworks/ArcDAT.cs b/ArcFormats/Cyberworks/ArcDAT.cs index 0ef34188..2f8f689e 100644 --- a/ArcFormats/Cyberworks/ArcDAT.cs +++ b/ArcFormats/Cyberworks/ArcDAT.cs @@ -205,7 +205,10 @@ namespace GameRes.Formats.Cyberworks string game_name = arc_name != "Arc06.dat" ? TryParseMeta (VFS.CombinePath (dir_name, "Arc06.dat")) : null; Tuple parsed = null; if (string.IsNullOrEmpty (game_name)) + { + game_name = TryParseMeta (VFS.CombinePath (dir_name, "Arc00.dat")); parsed = s_name_parsers.Select (p => p.ParseName (arc_name)).FirstOrDefault (p => p != null); + } else // Shukujo no Tsuyagoto special case parsed = OldDatOpener.ArcNameParser.ParseName (arc_name); if (null == parsed) @@ -217,7 +220,7 @@ namespace GameRes.Formats.Cyberworks var toc = ReadToc (toc_name, 8); if (null == toc) return null; - using (var index = new ArcIndexReader (toc, file, arc_idx)) + using (var index = new ArcIndexReader (toc, file, arc_idx, game_name)) { if (!index.Read()) return null; @@ -311,10 +314,8 @@ namespace GameRes.Formats.Cyberworks if ('c' == type || 'b' == type) { uint img_size = Binary.BigEndian (input.ReadUInt32()); - if (input.Length - 5 == img_size) - { - input = BinaryStream.FromStream (new StreamRegion (input.AsStream, 5, img_size), input.Name); - } + long start_pos = input.Length - img_size; + input = BinaryStream.FromStream (new StreamRegion (input.AsStream, start_pos, img_size), input.Name); } else if (scheme != null && ('a' == type || 'd' == type) && input.Length > 21) { @@ -618,9 +619,13 @@ namespace GameRes.Formats.Cyberworks return true; } + uint m_fault_id = 100000; + internal PackedEntry ReadEntryInfo () { uint id = m_index.ReadUInt32(); + if (id > m_fault_id) + id = m_fault_id++; var entry = new PackedEntry { Name = id.ToString ("D6") }; entry.UnpackedSize = m_index.ReadUInt32(); entry.Size = m_index.ReadUInt32(); @@ -650,10 +655,14 @@ namespace GameRes.Formats.Cyberworks internal class ArcIndexReader : IndexReader { int m_arc_number; + string m_game_name; + bool m_ignore_b_files = false; - public ArcIndexReader (byte[] toc, ArcView file, int arc_number) : base (toc, file) + public ArcIndexReader (byte[] toc, ArcView file, int arc_number, string game_name = null) : base (toc, file) { m_arc_number = arc_number; + m_game_name = game_name; + m_ignore_b_files = m_game_name == "ドキドキ母娘レッスン ~教えて♪Hなお勉強~"; } char[] m_type = new char[2]; @@ -677,7 +686,7 @@ namespace GameRes.Formats.Cyberworks ext = new string (m_type); else ext = new string (m_type[0], 1); - if ("b0" == ext || "n0" == ext || "o0" == ext || "0b" == ext || "b" == ext) + if ("b0" == ext || "n0" == ext || "o0" == ext || "0b" == ext || ("b" == ext && !m_ignore_b_files)) { entry.Type = "image"; HasImages = true; diff --git a/ArcFormats/Cyberworks/ImageTINK.cs b/ArcFormats/Cyberworks/ImageTINK.cs index b806d8b1..001f0b39 100644 --- a/ArcFormats/Cyberworks/ImageTINK.cs +++ b/ArcFormats/Cyberworks/ImageTINK.cs @@ -2,7 +2,7 @@ //! \date Fri Jun 17 18:49:04 2016 //! \brief Tinker Bell encrypted image file. // -// Copyright (C) 2016-2017 by morkt +// Copyright (C) 2016-2022 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 @@ -28,6 +28,7 @@ using System.Collections.Generic; using System.IO; using System.Windows.Media; using System.Windows.Media.Imaging; +using GameRes.Utility; namespace GameRes.Formats.Cyberworks { @@ -145,9 +146,17 @@ namespace GameRes.Formats.Cyberworks { var size_buf = new byte[4]; input.Read (size_buf, 0 , 4); - var decoder = new PngBitmapDecoder (input, BitmapCreateOptions.None, - BitmapCacheOption.OnLoad); - BitmapSource frame = decoder.Frames[0]; + int png_size = BigEndian.ToInt32 (size_buf, 0); + BitmapSource frame; + // work-around for possible extra padding before PNG data + using (var membuf = new MemoryStream (png_size+4)) + { + input.CopyTo (membuf); + membuf.Seek (-png_size, SeekOrigin.End); + var decoder = new PngBitmapDecoder (membuf, BitmapCreateOptions.None, + BitmapCacheOption.OnLoad); + frame = decoder.Frames[0]; + } Info.Width = (uint)frame.PixelWidth; Info.Height = (uint)frame.PixelHeight; if (frame.Format.BitsPerPixel != 32) diff --git a/ArcFormats/DraftArc.cs b/ArcFormats/DraftArc.cs index 694c2850..f658fe9a 100644 --- a/ArcFormats/DraftArc.cs +++ b/ArcFormats/DraftArc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2019 by morkt +// Copyright (C) 2022 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 diff --git a/ArcFormats/DraftAudio.cs b/ArcFormats/DraftAudio.cs index f8a034a7..a083c247 100644 --- a/ArcFormats/DraftAudio.cs +++ b/ArcFormats/DraftAudio.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2019 by morkt +// Copyright (C) 2022 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 diff --git a/ArcFormats/DraftImage.cs b/ArcFormats/DraftImage.cs index 78a08afa..ddf72596 100644 --- a/ArcFormats/DraftImage.cs +++ b/ArcFormats/DraftImage.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2019 by morkt +// Copyright (C) 2022 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 diff --git a/ArcFormats/Hypatia/ArcLPC.cs b/ArcFormats/Hypatia/ArcLPC.cs new file mode 100644 index 00000000..01c67685 --- /dev/null +++ b/ArcFormats/Hypatia/ArcLPC.cs @@ -0,0 +1,61 @@ +//! \file ArcLPC.cs +//! \date 2019 Jan 15 +//! \brief Kogado Stduio multi-frame image. +// +// Copyright (C) 2019 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Kogado +{ + [Export(typeof(ArchiveFormat))] + public class LpcOpener : ArchiveFormat + { + public override string Tag { get { return "LPC"; } } + public override string Description { get { return "Kogado Studio multi-frame image"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.HasExtension ("LPC")) + return null; + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = Create (name); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/ArcFormats/Hypatia/ArcLPK.cs b/ArcFormats/Hypatia/ArcLPK.cs new file mode 100644 index 00000000..7c61670b --- /dev/null +++ b/ArcFormats/Hypatia/ArcLPK.cs @@ -0,0 +1,78 @@ +//! \file ArcLPK.cs +//! \date 2022 Apr 12 +//! \brief Kogado resource archive. +// +// Copyright (C) 2022 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Kogado +{ + [Export(typeof(ArchiveFormat))] + public class LpkOpener : ArchiveFormat + { + public override string Tag { get { return "LPK/KOGADO"; } } + public override string Description { get { return "Kogado resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.HasExtension (".lpk") || file.MaxOffset < 0x2800) + return null; + uint index_offset = 0; + var dir = new List (); + for (int i = 0; i < 0x200; ++i) + { + if (file.View.ReadByte (index_offset) == 0) + break; + var name = file.View.ReadString (index_offset, 0x10); + if (!IsValidEntryName (name)) + return null; + index_offset += 0x10; + var entry = Create (name); + dir.Add (entry); + } + if (0 == dir.Count) + return null; + index_offset = 0x2000; + uint base_offset = 0x2800; + uint offset = file.View.ReadUInt32 (index_offset); + foreach (var entry in dir) + { + index_offset += 4; + uint next_offset = file.View.ReadUInt32 (index_offset); + uint size = next_offset - offset; + entry.Offset = offset + base_offset; + entry.Size = size; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + offset = next_offset; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/ArcFormats/Hypatia/ImageLSG.cs b/ArcFormats/Hypatia/ImageLSG.cs new file mode 100644 index 00000000..8a5604d2 --- /dev/null +++ b/ArcFormats/Hypatia/ImageLSG.cs @@ -0,0 +1,100 @@ +//! \file ImageLSG.cs +//! \date 2022 Apr 12 +//! \brief Kogado image format. +// +// Copyright (C) 2022 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Kogado +{ + internal class LsgMetaData : ImageMetaData + { + public int BitmapSize; + } + + [Export(typeof(ImageFormat))] + public class LsgFormat : ImageFormat + { + public override string Tag { get { return "LSG"; } } + public override string Description { get { return "Kogado image format"; } } + public override uint Signature { get { return 0x4D42; } } // 'BM' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x14); + var info = new LsgMetaData { + Width = header.ToUInt32 (0x0C), + Height = header.ToUInt32 (0x10), + BPP = header.ToInt32 (8), + BitmapSize = header.ToInt32 (4), + }; + if (info.BPP != 8 && info.BPP != 24) + return null; + return info; + } + + static readonly string DefaultPaletteName = "base.pal"; + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var meta = (LsgMetaData)info; + file.Position = 0x14; + var pixels = file.ReadBytes (meta.BitmapSize); + PixelFormat format; + BitmapPalette palette = null; + if (meta.BPP == 8) + { + format = PixelFormats.Indexed8; + palette = ReadDefaultPalette (file.Name); + if (null == palette) + format = PixelFormats.Gray8; + } + else + { + format = PixelFormats.Bgr24; + } + return ImageData.Create (info, format, palette, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("LsgFormat.Write not implemented"); + } + + internal BitmapPalette ReadDefaultPalette (string filename) + { + var pal_name = Path.ChangeExtension (filename, ".pal"); + if (!VFS.FileExists (pal_name)) + pal_name = VFS.ChangeFileName (filename, DefaultPaletteName); + if (!VFS.FileExists (pal_name)) + return null; + using (var input = VFS.OpenStream (pal_name)) + { + return ReadPalette (input, 0x100, PaletteFormat.Rgb); + } + } + } +} diff --git a/ArcFormats/Kaguya/ArcANM.cs b/ArcFormats/Kaguya/ArcANM.cs index a4d9145c..eaa38cde 100644 --- a/ArcFormats/Kaguya/ArcANM.cs +++ b/ArcFormats/Kaguya/ArcANM.cs @@ -23,7 +23,6 @@ // IN THE SOFTWARE. // -using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; @@ -42,60 +41,110 @@ namespace GameRes.Formats.Kaguya } } - [Export(typeof(ArchiveFormat))] - public class AnmOpener : ArchiveFormat + internal class AnmEntry : Entry + { + public long ImageDataOffset; + public uint ImageDataSize; + } + + internal interface IAnmReader + { + List GetFramesList (IBinaryStream input); + } + + public abstract class AnmOpenerBase : ArchiveFormat, IAnmReader { - public override string Tag { get { return "ANM/KAGUYA"; } } public override string Description { get { return "KaGuYa script engine animation resource"; } } - public override uint Signature { get { return 0x30304E41; } } // 'AN00' public override bool IsHierarchic { get { return false; } } public override bool CanWrite { get { return false; } } - public AnmOpener () + public AnmOpenerBase () { Extensions = new string[] { "anm" }; } public override ArcFile TryOpen (ArcView file) { - int frame_count = file.View.ReadInt16 (0x14); - uint current_offset = 0x18 + (uint)frame_count * 4; - int count = file.View.ReadInt16 (current_offset); - if (!IsSaneCount (count)) - return null; - var base_info = new ImageMetaData + using (var input = file.CreateStream()) { - OffsetX = file.View.ReadInt32 (4), - OffsetY = file.View.ReadInt32 (8), - Width = file.View.ReadUInt32 (0x0C), - Height = file.View.ReadUInt32 (0x10), - BPP = 32, - }; - current_offset += 2; - string base_name = Path.GetFileNameWithoutExtension (file.Name); - var dir = new List (count); - for (int i = 0; i < count; ++i) - { - uint width = file.View.ReadUInt32 (current_offset+8); - uint height = file.View.ReadUInt32 (current_offset+12); - var entry = new Entry + var dir = GetFramesList (input); + if (null == dir) + return null; + var base_info = GetBaseInfo (input); + string base_name = Path.GetFileNameWithoutExtension (file.Name); + int i = 0; + foreach (var entry in dir) { - Name = string.Format ("{0}#{1:D2}", base_name, i), - Type = "image", - Offset = current_offset, - Size = 0x10 + 4*width*height, - }; - dir.Add (entry); - current_offset += entry.Size; + entry.Name = string.Format ("{0}#{1:D2}", base_name, i++); + entry.Type = "image"; + } + return new AnmArchive (file, this, dir, base_info); } - return new AnmArchive (file, this, dir, base_info); } public override IImageDecoder OpenImage (ArcFile arc, Entry entry) { var base_info = ((AnmArchive)arc).ImageInfo; var input = arc.File.CreateStream (entry.Offset, entry.Size); - return new An00Decoder (input, base_info); + return CreateDecoder (input, base_info); + } + + internal virtual ImageMetaData GetBaseInfo (IBinaryStream input) + { + input.Position = 4; + return new ImageMetaData + { + OffsetX = input.ReadInt32(), + OffsetY = input.ReadInt32(), + Width = input.ReadUInt32(), + Height = input.ReadUInt32(), + BPP = 32, + }; + } + + public abstract List GetFramesList (IBinaryStream input); + + public abstract IImageDecoder CreateDecoder (IBinaryStream input, ImageMetaData info); + } + + [Export(typeof(ArchiveFormat))] + public class AnmOpener : AnmOpenerBase + { + public override string Tag { get { return "ANM/KAGUYA"; } } + public override uint Signature { get { return 0x30304E41; } } // 'AN00' + + public override List GetFramesList (IBinaryStream file) + { + file.Position = 0x14; + int frame_count = file.ReadInt16(); + file.Position = 0x18 + frame_count * 4; + int count = file.ReadInt16(); + if (!IsSaneCount (count)) + return null; + var current_offset = file.Position; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + file.Position = current_offset + 8; + uint width = file.ReadUInt32(); + uint height = file.ReadUInt32(); + uint image_size = 4*width*height; + var entry = new AnmEntry + { + Offset = current_offset, + Size = 0x10 + image_size, + ImageDataOffset = current_offset + 0x10, + ImageDataSize = image_size, + }; + dir.Add (entry); + current_offset += entry.Size; + } + return dir; + } + + public override IImageDecoder CreateDecoder (IBinaryStream input, ImageMetaData info) + { + return new An00Decoder (input, info); } } @@ -123,75 +172,146 @@ namespace GameRes.Formats.Kaguya } [Export(typeof(ArchiveFormat))] - public class An20Opener : ArchiveFormat + public class An10Opener : AnmOpenerBase, IAnmReader { - public override string Tag { get { return "AN20/KAGUYA"; } } - public override string Description { get { return "KaGuYa script engine animation resource"; } } - public override uint Signature { get { return 0x30324E41; } } // 'AN20' - public override bool IsHierarchic { get { return false; } } - public override bool CanWrite { get { return false; } } + public override string Tag { get { return "AN10/KAGUYA"; } } + public override uint Signature { get { return 0x30314E41; } } // 'AN10' - public An20Opener () + public override List GetFramesList (IBinaryStream file) { - Extensions = new string[] { "anm" }; - } - - public override ArcFile TryOpen (ArcView file) - { - int table_count = file.View.ReadInt16 (4); - uint current_offset = 8; - for (int i = 0; i < table_count; ++i) - { - switch (file.View.ReadByte (current_offset++)) - { - case 0: break; - case 1: current_offset += 8; break; - case 2: - case 3: - case 4: - case 5: current_offset += 4; break; - default: return null; - } - } - current_offset += 2 + file.View.ReadUInt16 (current_offset) * 8u; - int count = file.View.ReadInt16 (current_offset); + file.Position = 0x14; + int frame_count = file.ReadInt16(); + file.Position = 0x18 + frame_count * 4; + int count = file.ReadInt16(); if (!IsSaneCount (count)) return null; - current_offset += 2; - var base_info = new ImageMetaData - { - OffsetX = file.View.ReadInt32 (current_offset), - OffsetY = file.View.ReadInt32 (current_offset+4), - Width = file.View.ReadUInt32 (current_offset+8), - Height = file.View.ReadUInt32 (current_offset+12), - BPP = 32, - }; - current_offset += 0x10; - string base_name = Path.GetFileNameWithoutExtension (file.Name); + var current_offset = file.Position; var dir = new List (count); for (int i = 0; i < count; ++i) { - uint width = file.View.ReadUInt32 (current_offset+8); - uint height = file.View.ReadUInt32 (current_offset+0x0C); - uint depth = file.View.ReadUInt32 (current_offset+0x10); - var entry = new Entry + file.Position = current_offset + 8; + uint width = file.ReadUInt32(); + uint height = file.ReadUInt32(); + uint channels = file.ReadUInt32(); + uint image_size = channels*width*height; + var entry = new AnmEntry { - Name = string.Format ("{0}#{1:D2}", base_name, i), - Type = "image", Offset = current_offset, - Size = 0x14 + depth*width*height, + Size = 0x14 + image_size, + ImageDataOffset = current_offset + 0x14, + ImageDataSize = image_size, }; dir.Add (entry); current_offset += entry.Size; } - return new AnmArchive (file, this, dir, base_info); + return dir; } - public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + public override IImageDecoder CreateDecoder (IBinaryStream input, ImageMetaData info) { - var base_info = ((AnmArchive)arc).ImageInfo; - var input = arc.File.CreateStream (entry.Offset, entry.Size); - return new An20Decoder (input, base_info); + return new An10Decoder (input, info); + } + } + + internal class An10Decoder : BinaryImageDecoder + { + public An10Decoder (IBinaryStream input, ImageMetaData base_info) : base (input) + { + Info = new ImageMetaData + { + OffsetX = base_info.OffsetX + m_input.ReadInt32(), + OffsetY = base_info.OffsetY + m_input.ReadInt32(), + Width = m_input.ReadUInt32(), + Height = m_input.ReadUInt32(), + BPP = m_input.ReadInt32() * 8, + }; + } + + protected override ImageData GetImageData () + { + m_input.Position = 0x14; + int stride = Info.BPP / 8 * Info.iWidth; + var pixels = m_input.ReadBytes (stride*Info.iHeight); + PixelFormat format = 24 == Info.BPP ? PixelFormats.Bgr24 : PixelFormats.Bgra32; + return ImageData.CreateFlipped (Info, format, null, pixels, stride); + } + } + + [Export(typeof(ArchiveFormat))] + public class An20Opener : AnmOpenerBase + { + public override string Tag { get { return "AN20/KAGUYA"; } } + public override uint Signature { get { return 0x30324E41; } } // 'AN20' + + public override List GetFramesList (IBinaryStream file) + { + if (!SkipFrameTable (file)) + return null; + int count = file.ReadInt16(); + if (!IsSaneCount (count)) + return null; + long current_offset = file.Position + 0x10; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + file.Position = current_offset + 8; + uint width = file.ReadUInt32(); + uint height = file.ReadUInt32(); + uint depth = file.ReadUInt32(); + uint image_size = depth*width*height; + var entry = new AnmEntry + { + Offset = current_offset, + Size = 0x14 + image_size, + ImageDataOffset = current_offset + 0x14, + ImageDataSize = image_size, + }; + dir.Add (entry); + current_offset += entry.Size; + } + return dir; + } + + internal override ImageMetaData GetBaseInfo (IBinaryStream input) + { + SkipFrameTable (input); + input.ReadInt16(); + return new ImageMetaData + { + OffsetX = input.ReadInt32(), + OffsetY = input.ReadInt32(), + Width = input.ReadUInt32(), + Height = input.ReadUInt32(), + BPP = 32, + }; + } + + bool SkipFrameTable (IBinaryStream file) + { + file.Position = 4; + int table_count = file.ReadInt16(); + file.Position = 8; + for (int i = 0; i < table_count; ++i) + { + switch (file.ReadByte()) + { + case 0: break; + case 1: file.Seek (8, SeekOrigin.Current); break; + case 2: + case 3: + case 4: + case 5: file.Seek (4, SeekOrigin.Current); break; + default: return false; + } + } + int count = file.ReadUInt16(); + file.Seek (count * 8, SeekOrigin.Current); + return true; + } + + public override IImageDecoder CreateDecoder (IBinaryStream input, ImageMetaData info) + { + return new An20Decoder (input, info); } } diff --git a/ArcFormats/Kaguya/ArcLIN2.cs b/ArcFormats/Kaguya/ArcLIN2.cs index 701f17ef..fb09a387 100644 --- a/ArcFormats/Kaguya/ArcLIN2.cs +++ b/ArcFormats/Kaguya/ArcLIN2.cs @@ -90,7 +90,7 @@ namespace GameRes.Formats.Kaguya } } - byte[] UnpackLzss (IBinaryStream input, uint unpacked_size) + internal static byte[] UnpackLzss (IBinaryStream input, uint unpacked_size) { var output = new byte[unpacked_size]; var frame = new byte[0x100]; diff --git a/ArcFormats/Kaguya/ArcLINK.cs b/ArcFormats/Kaguya/ArcLINK.cs index ec0f5294..7001bf5a 100644 --- a/ArcFormats/Kaguya/ArcLINK.cs +++ b/ArcFormats/Kaguya/ArcLINK.cs @@ -65,7 +65,7 @@ namespace GameRes.Formats.Kaguya { int version = file.View.ReadByte (4) - '0'; if (version < 3 || version > 6) - return null; + return ReadOldIndex (file); using (var reader = LinkReader.Create (file, version)) { @@ -87,7 +87,22 @@ namespace GameRes.Formats.Kaguya { var lent = entry as LinkEntry; if (null == lent || (!lent.IsPacked && !lent.IsEncrypted)) + { + if (entry.Size > 8) + { + uint unpacked_size = arc.File.View.ReadUInt32 (entry.Offset); + int id = arc.File.View.ReadUInt16 (entry.Offset+5); + if (id == 0x4D42) // 'BM' + { + using (var input = arc.File.CreateStream (entry.Offset+4, entry.Size-4, entry.Name)) + { + var data = Lin2Opener.UnpackLzss (input, unpacked_size); + return new BinMemoryStream (data, entry.Name); + } + } + } return base.OpenEntry (arc, entry); + } if (lent.IsEncrypted) { var larc = arc as LinkArchive; @@ -102,6 +117,35 @@ namespace GameRes.Formats.Kaguya return new BinMemoryStream (bmr.Data, entry.Name); } } + + internal ArcFile ReadOldIndex (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + + var dir = new List (count); + using (var index = file.CreateStream()) + { + index.Position = 8; + uint names_size = index.ReadUInt32(); + for (int i = 0; i < count; ++i) + { + var name = index.ReadCString(); + var entry = FormatCatalog.Instance.Create (name); + dir.Add (entry); + } + index.Position = 12 + names_size; + foreach (var entry in dir) + { + entry.Offset = index.ReadUInt32(); + entry.Size = index.ReadUInt32(); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + } + } + return new ArcFile (file, this, dir); + } } internal class LinkReader : IDisposable @@ -440,9 +484,15 @@ namespace GameRes.Formats.Kaguya var header = input.ReadHeader (0x11); if (header.AsciiEqual ("[SCR-PARAMS]v0")) { - var version = Version.Parse (header.GetCString (13, 4)); + Version version; + if ('.' == header[15]) + version = Version.Parse (header.GetCString (13, 4)); + else + version = new Version (header[14] - '0', 0); if (2 == version.Major) return new ParamsV2Deserializer (input, version); + else if (version.Major < 5) + return new ParamsV4Deserializer (input, version); else if (5 == version.Major && (version.Minor >= 4 && version.Minor <= 7)) return new ParamsV5Deserializer (input, version); } @@ -499,6 +549,22 @@ namespace GameRes.Formats.Kaguya SkipString(); } } + + protected void ReadHeader (int start) + { + m_input.Position = start; + SkipChunk(); + m_title = ReadString(); + if (m_version.Major < 3) + m_input.ReadCString(); + SkipString(); + SkipString(); + m_input.ReadByte(); + SkipString(); + SkipString(); + SkipDict(); + m_input.ReadByte(); + } } internal class ParamsV2Deserializer : ParamsDeserializer @@ -515,17 +581,7 @@ namespace GameRes.Formats.Kaguya public override byte[] GetKey () { - m_input.Position = 0x17; - SkipChunk(); - m_title = ReadString(); - m_input.ReadCString(); - SkipString(); - SkipString(); - m_input.ReadByte(); - SkipString(); - SkipString(); - SkipDict(); - m_input.ReadByte(); + ReadHeader (0x17); if ("幼なじみと甘~くエッチに過ごす方法" == m_title) { @@ -565,6 +621,37 @@ namespace GameRes.Formats.Kaguya } } + internal class ParamsV4Deserializer : ParamsDeserializer + { + public ParamsV4Deserializer (IBinaryStream input, Version version) : base (input, version) + { + } + + public override byte[] GetKey () + { + ReadHeader (0x19); + + Skip (m_version.Major < 5 ? 12 : 11); + int count = m_input.ReadUInt8(); + for (int i = 0; i < count; ++i) + { + m_input.ReadByte(); + SkipChunk(); + SkipArray(); + SkipArray(); + } + SkipDict(); + count = m_input.ReadUInt8(); + for (int i = 0; i < count; ++i) + { + SkipChunk(); + SkipArray(); + SkipArray(); + } + return ReadKey(); + } + } + internal class ParamsV5Deserializer : ParamsDeserializer { public ParamsV5Deserializer (IBinaryStream input, Version version) : base (input, version) @@ -573,18 +660,9 @@ namespace GameRes.Formats.Kaguya public override byte[] GetKey () { - // ハラミタマ - m_input.Position = 0x1B; - SkipChunk(); - m_title = ReadString(); - SkipString(); - SkipString(); - m_input.ReadByte(); - SkipString(); - SkipString(); - SkipDict(); + ReadHeader (0x1B); - Skip (m_version.Minor <= 4 ? 0x10 : 0x11); + Skip (m_version.Minor <= 4 ? 15 : 16); for (int i = 0; i < 3; ++i) { if (0 != m_input.ReadUInt8()) @@ -633,6 +711,11 @@ namespace GameRes.Formats.Kaguya delegate Stream Decryptor (LinkArchive arc, LinkEntry entry); + static readonly ResourceInstance An00 = new ResourceInstance ("ANM/KAGUYA"); + static readonly ResourceInstance An10 = new ResourceInstance ("AN10/KAGUYA"); + static readonly ResourceInstance An20 = new ResourceInstance ("AN20/KAGUYA"); + static readonly ResourceInstance Pl00 = new ResourceInstance ("PLT/KAGUYA"); + public LinkEncryption (byte[] key, bool anm_encrypted = true) { if (null == key || 0 == key.Length) @@ -675,13 +758,12 @@ namespace GameRes.Formats.Kaguya return new PrefixStream (header, body); } - Stream DecryptAn00 (LinkArchive arc, LinkEntry entry) + Stream DecryptAnm (LinkArchive arc, LinkEntry entry, IAnmReader reader) { var data = arc.File.View.ReadBytes (entry.Offset, entry.Size); - int frame_offset = 0x18 + data.ToUInt16 (0x14) * 4; - int count = data.ToUInt16 (frame_offset); - frame_offset += 10; - for (int i = 0; i < count; ++i) + var input = new BinMemoryStream (data, entry.Name); + var dir = reader.GetFramesList (input); + if (dir != null) { int w = data.ToInt32 (frame_offset); int h = data.ToInt32 (frame_offset+4); @@ -691,8 +773,8 @@ namespace GameRes.Formats.Kaguya frame_offset += size + 8; } return new BinMemoryStream (data, entry.Name); - } - + } + Stream DecryptAn20(LinkArchive arc, LinkEntry entry) { var data = arc.File.View.ReadBytes(entry.Offset, entry.Size); @@ -701,13 +783,13 @@ namespace GameRes.Formats.Kaguya for (int i = 0; i < count; ++i) { switch (data[offset++]) - { - case 0: break; - case 1: offset += 8; break; - case 2: - case 3: - case 4: - case 5: offset += 4; break; + { + case 0: break; + case 1: offset += 8; break; + case 2: + case 3: + case 4: + case 5: offset += 4; break; default: return new BinMemoryStream(data, entry.Name); } } @@ -715,16 +797,16 @@ namespace GameRes.Formats.Kaguya offset += 2 + count * 8; int frame_count = data.ToInt16(offset); offset += 18; - for(int i = 0; i < frame_count; ++i) - { - offset += 8; - int w = data.ToInt32(offset); - int h = data.ToInt32(offset + 4); - int channels = data.ToInt32(offset + 8); - int frame_size = channels * w * h; - offset += 12; - DecryptData(data, offset, frame_size); - offset += frame_size; + for(int i = 0; i < frame_count; ++i) + { + offset += 8; + int w = data.ToInt32(offset); + int h = data.ToInt32(offset + 4); + int channels = data.ToInt32(offset + 8); + int frame_size = channels * w * h; + offset += 12; + DecryptData(data, offset, frame_size); + offset += frame_size; } return new BinMemoryStream(data, entry.Name); } @@ -755,27 +837,27 @@ namespace GameRes.Formats.Kaguya offset += 12; DecryptData (data, offset, channels * w * h); return new BinMemoryStream (data, entry.Name); - } - + } + Stream DecryptPL00(LinkArchive arc, LinkEntry entry) { var data = arc.File.View.ReadBytes(entry.Offset, entry.Size); int count = data.ToUInt16(4); int offset = 22; - for(int i = 0; i < count; ++i) - { - offset += 8; - int w = data.ToInt32(offset); - int h = data.ToInt32(offset + 4); - int channels = data.ToInt32(offset + 8); - int size = channels * w * h; - offset += 12; - DecryptData(data, offset, size); - offset += size; + for(int i = 0; i < count; ++i) + { + offset += 8; + int w = data.ToInt32(offset); + int h = data.ToInt32(offset + 4); + int channels = data.ToInt32(offset + 8); + int size = channels * w * h; + offset += 12; + DecryptData(data, offset, size); + offset += size; } return new BinMemoryStream(data, entry.Name); - } - + } + Stream DecryptPL10(LinkArchive arc, LinkEntry entry) { var data = arc.File.View.ReadBytes(entry.Offset, entry.Size); diff --git a/ArcFormats/Kaguya/ArcPLT.cs b/ArcFormats/Kaguya/ArcPLT.cs new file mode 100644 index 00000000..bc2bcdc5 --- /dev/null +++ b/ArcFormats/Kaguya/ArcPLT.cs @@ -0,0 +1,101 @@ +//! \file ArcPLT.cs +//! \date 2022 May 03 +//! \brief KaGuYa script engine animation resource. +// +// Copyright (C) 2022 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.Windows.Media; + +namespace GameRes.Formats.Kaguya +{ + [Export(typeof(ArchiveFormat))] + public class Pl00Opener : AnmOpenerBase + { + public override string Tag { get { return "PLT/KAGUYA"; } } + public override uint Signature { get { return 0x30304C50; } } // 'PL00' + + public Pl00Opener () + { + Extensions = new string[] { "plt" }; + } + + public override List GetFramesList (IBinaryStream file) + { + file.Position = 4; + int count = file.ReadInt16(); + if (!IsSaneCount (count)) + return null; + file.Position = 0x16; + var current_offset = file.Position; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + file.Position = current_offset + 8; + uint width = file.ReadUInt32(); + uint height = file.ReadUInt32(); + uint depth = file.ReadUInt32(); + uint image_size = depth*width*height; + var entry = new AnmEntry + { + Offset = current_offset, + Size = 0x14 + image_size, + ImageDataOffset = current_offset + 0x14, + ImageDataSize = image_size, + }; + dir.Add (entry); + current_offset += entry.Size; + } + return dir; + } + + public override IImageDecoder CreateDecoder (IBinaryStream input, ImageMetaData info) + { + return new Pl00Decoder (input, info); + } + } + + internal class Pl00Decoder : BinaryImageDecoder + { + public Pl00Decoder (IBinaryStream input, ImageMetaData base_info) : base (input) + { + Info = new ImageMetaData + { + OffsetX = base_info.OffsetX + m_input.ReadInt32(), + OffsetY = base_info.OffsetY + m_input.ReadInt32(), + Width = m_input.ReadUInt32(), + Height = m_input.ReadUInt32(), + BPP = m_input.ReadInt32() * 8, + }; + } + + protected override ImageData GetImageData () + { + m_input.Position = 0x14; + int stride = Info.BPP * Info.iWidth / 8; + var pixels = m_input.ReadBytes (stride*Info.iHeight); + PixelFormat format = 24 == Info.BPP ? PixelFormats.Bgr24 : PixelFormats.Bgra32; + return ImageData.CreateFlipped (Info, format, null, pixels, stride); + } + } +} diff --git a/ArcFormats/Liar/ArcXFL.cs b/ArcFormats/Liar/ArcXFL.cs index a9a9314e..dcfc7bca 100644 --- a/ArcFormats/Liar/ArcXFL.cs +++ b/ArcFormats/Liar/ArcXFL.cs @@ -39,38 +39,62 @@ namespace GameRes.Formats.Liar 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 IsHierarchic { get { return true; } } public override bool CanWrite { 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) + var dir = ReadDirectory (file, 0, file.MaxOffset, ""); + if (dir != null) + return new ArcFile (file, this, dir); + else return null; - long max_offset = file.MaxOffset; - uint base_offset = dir_size + 12; - if (dir_size >= max_offset || base_offset >= max_offset) + } + + internal List ReadDirectory (ArcView file, long base_offset, long max_offset, string base_dir) + { + uint dir_size = file.View.ReadUInt32 (base_offset+4); + int count = file.View.ReadInt32 (base_offset+8); + if (!IsSaneCount (count)) + return null; + long data_offset = base_offset + dir_size + 12; + if (dir_size >= max_offset || data_offset >= max_offset) return null; - file.View.Reserve (0, base_offset); - long cur_offset = 12; + file.View.Reserve (base_offset, (uint)(data_offset - base_offset)); + long cur_offset = base_offset + 12; var dir = new List (count); for (int i = 0; i < count; ++i) { - if (cur_offset+40 > base_offset) + if (cur_offset+40 > data_offset) return null; string name = file.View.ReadString (cur_offset, 32); - var entry = FormatCatalog.Instance.Create (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); + var entry_offset = data_offset + file.View.ReadUInt32 (cur_offset+32); + var entry_size = file.View.ReadUInt32 (cur_offset+36); + List subdir = null; + name = VFS.CombinePath (base_dir, name); + if (name.HasExtension (".xfl") && file.View.ReadUInt32 (entry_offset) == Signature) + { + subdir = ReadDirectory (file, entry_offset, entry_offset + entry_size, name); + } + if (subdir != null && subdir.Count > 0) + { + dir.AddRange (subdir); + } + else + { + + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = entry_offset; + entry.Size = entry_size; + if (!entry.CheckPlacement (max_offset)) + return null; + dir.Add (entry); + } cur_offset += 40; } - return new ArcFile (file, this, dir); + return dir; } public override void Create (Stream output, IEnumerable list, ResourceOptions options, diff --git a/ArcFormats/Maika/ArcMK2.cs b/ArcFormats/Maika/ArcMK2.cs index 3fc390f2..7214a5e5 100644 --- a/ArcFormats/Maika/ArcMK2.cs +++ b/ArcFormats/Maika/ArcMK2.cs @@ -61,9 +61,9 @@ namespace GameRes.Formats.Maika public Mk2Opener () { - // 'MK2.0' 'BL2.0'. 'SL1.0', 'LS2.0', 'AR2.0' + // 'MK2.0' 'BL2.0'. 'SL1.0', 'LS2.0', 'AR2.0', 'MP2.0' Signatures = new uint[] { - 0x2E324B4D, 0x2E324C42, 0x2E314C53, 0x2E32534C, 0x2E325241 + 0x2E324B4D, 0x2E324C42, 0x2E314C53, 0x2E32534C, 0x2E325241, 0x2E32504D }; } diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index df1ffa68..a685200d 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.2.48.2153")] -[assembly: AssemblyFileVersion ("1.2.48.2153")] +[assembly: AssemblyVersion ("1.2.48.2176")] +[assembly: AssemblyFileVersion ("1.2.48.2176")] diff --git a/ArcFormats/Qlie/ArcQLIE.cs b/ArcFormats/Qlie/ArcQLIE.cs index dba29634..ae263489 100644 --- a/ArcFormats/Qlie/ArcQLIE.cs +++ b/ArcFormats/Qlie/ArcQLIE.cs @@ -84,7 +84,7 @@ namespace GameRes.Formats.Qlie public PackOpener () { Extensions = new string [] { "pack" }; - ContainedFormats = new[] { "ABMP/QLIE", "DPNG", "PNG", "JPEG", "OGG", "WAV" }; + ContainedFormats = new[] { "ABMP/QLIE", "DPNG", "ARGB", "PNG", "JPEG", "OGG", "WAV" }; } /// diff --git a/ArcFormats/Qlie/ImageARGB.cs b/ArcFormats/Qlie/ImageARGB.cs new file mode 100644 index 00000000..04eb77b4 --- /dev/null +++ b/ArcFormats/Qlie/ImageARGB.cs @@ -0,0 +1,114 @@ +//! \file ImageARGB.cs +//! \date 2022 Apr 22 +//! \brief QLIE image format. +// +// Copyright (C) 2022 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Qlie +{ + internal class ArgbMetaData : ImageMetaData + { + public uint ImageOffset; + public uint ImageLength; + public uint MaskLength; + } + + [Export(typeof(ImageFormat))] + public class ArgbFormat : ImageFormat + { + public override string Tag { get { return "ARGB"; } } + public override string Description { get { return "QLIE image format"; } } + public override uint Signature { get { return 0x42475241; } } // 'ARGB' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x19); + if (!header.AsciiEqual ("ARGBSaveData1\0") || header[0x10] != 3) + return null; + const uint image_offset = 0x19; + uint image_size = header.ToUInt32 (0x11); + uint mask_size = header.ToUInt32 (0x15); + using (var jpeg = OpenStreamRegion (file, image_offset, image_size)) + { + var info = Jpeg.ReadMetaData (jpeg); + if (null == info) + return null; + return new ArgbMetaData { + Width = info.Width, + Height = info.Height, + BPP = 32, + ImageOffset = image_offset, + ImageLength = image_size, + MaskLength = mask_size, + }; + } + } + + internal IBinaryStream OpenStreamRegion (IBinaryStream file, long offset, uint length) + { + var input = new StreamRegion (file.AsStream, offset, length, true); + return new BinaryStream (input, file.Name); + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var meta = (ArgbMetaData)info; + file.Position = meta.ImageOffset; + var jpeg = new JpegBitmapDecoder (file.AsStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + BitmapSource bitmap = jpeg.Frames[0]; + + file.Position = meta.ImageOffset + meta.ImageLength; + var png = new PngBitmapDecoder (file.AsStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + BitmapSource mask = png.Frames[0]; + if (mask.PixelWidth != bitmap.PixelWidth || mask.PixelHeight != bitmap.PixelHeight) + throw new InvalidFormatException ("ARGB bitmap and mask dimensions mismatch"); + + if (bitmap.Format.BitsPerPixel != 32) + bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgr32, null, 0); + int stride = bitmap.PixelWidth * 4; + var pixels = new byte[stride * bitmap.PixelHeight]; + bitmap.CopyPixels (pixels, stride, 0); + + if (mask.Format.BitsPerPixel != 8) + mask = new FormatConvertedBitmap (mask, PixelFormats.Gray8, null, 0); + var alpha = new byte[mask.PixelWidth * mask.PixelHeight]; + mask.CopyPixels (alpha, mask.PixelWidth, 0); + + int src = 0; + for (int dst = 3; dst < pixels.Length; dst += 4) + { + pixels[dst] = alpha[src++]; + } + return ImageData.Create (info, PixelFormats.Bgra32, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("ArgbFormat.Write not implemented"); + } + } +} diff --git a/ArcFormats/Seraphim/ArcArchAngel.cs b/ArcFormats/Seraphim/ArcArchAngel.cs new file mode 100644 index 00000000..8b74130f --- /dev/null +++ b/ArcFormats/Seraphim/ArcArchAngel.cs @@ -0,0 +1,132 @@ +//! \file ArcArchAngel.cs +//! \date 2022 May 01 +//! \brief ArchAngel engine resource archive. +// +// Copyright (C) 2022 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.ArchAngel +{ + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag { get { return "DAT/ARCH"; } } + public override string Description { get { return "ArchAngel engine resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public DatOpener () + { + ContainedFormats = new[] { "CB" }; + } + + static readonly string[] DefaultSections = { "image", "script", null }; + + public override ArcFile TryOpen (ArcView file) + { + if (file.MaxOffset > uint.MaxValue + || !VFS.IsPathEqualsToFileName (file.Name, "ARCHPAC.DAT")) + return null; + int file_count = file.View.ReadInt16 (0); + if (!IsSaneCount (file_count)) + return null; + uint index_pos = 2; + var size_table = new uint[file_count]; + for (int i = 0; i < file_count; ++i) + { + size_table[i] = file.View.ReadUInt32 (index_pos); + index_pos += 4; + } + var section_table = new SortedDictionary(); + uint min_offset = (uint)file.MaxOffset; + while (index_pos + 6 <= min_offset) + { + uint offset = file.View.ReadUInt32 (index_pos); + int index = file.View.ReadInt16 (index_pos+4); + if (index < 0 || index > file_count || offset > file.MaxOffset) + return null; + if (offset < min_offset) + min_offset = offset; + section_table[index] = offset; + index_pos += 6; + } + var dir = new List (file_count); + int section_num = 0; + foreach (var section in section_table) + { + int i = section.Key; + uint base_offset = section.Value; + do + { + uint size = size_table[i]; + var entry = new PackedEntry { + Name = string.Format ("{0}-{1:D6}", section_num, i), + Offset = base_offset, + Size = size, + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + if (section_num < DefaultSections.Length && DefaultSections[section_num] != null) + entry.Type = DefaultSections[section_num]; + if ("script" == entry.Type) + entry.IsPacked = true; + dir.Add (entry); + base_offset += size; + ++i; + } + while (i < file_count && !section_table.ContainsKey (i)); + ++section_num; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (0 == entry.Size) + return Stream.Null; + var pent = entry as PackedEntry; + if (null == pent || !pent.IsPacked || pent.Size <= 4) + return base.OpenEntry (arc, entry); + var input = arc.File.CreateStream (entry.Offset, entry.Size); + if (0 == pent.UnpackedSize) + pent.UnpackedSize = input.Signature; + try + { + var data = Seraphim.ScnOpener.LzDecompress (input); + return new BinMemoryStream (data, entry.Name); + } + catch + { + return arc.File.CreateStream (entry.Offset, entry.Size); + } + finally + { + input.Dispose(); + } + } + } +} diff --git a/ArcFormats/Seraphim/ArcSCN.cs b/ArcFormats/Seraphim/ArcSCN.cs index 4a15f56e..304c6bac 100644 --- a/ArcFormats/Seraphim/ArcSCN.cs +++ b/ArcFormats/Seraphim/ArcSCN.cs @@ -112,7 +112,7 @@ namespace GameRes.Formats.Seraphim } } - internal byte[] LzDecompress (IBinaryStream input) + internal static byte[] LzDecompress (IBinaryStream input) { int unpacked_size = input.ReadInt32(); var data = new byte[unpacked_size]; diff --git a/ArcFormats/Seraphim/ArcSeraph.cs b/ArcFormats/Seraphim/ArcSeraph.cs index f9f765b0..7d2408b8 100644 --- a/ArcFormats/Seraphim/ArcSeraph.cs +++ b/ArcFormats/Seraphim/ArcSeraph.cs @@ -82,6 +82,7 @@ namespace GameRes.Formats.Seraphim public ArchPacOpener () { Extensions = new string[] { "dat" }; + ContainedFormats = new[] { "CB" }; } public override ArcFile TryOpen (ArcView file) diff --git a/ArcFormats/Seraphim/ImageSeraph.cs b/ArcFormats/Seraphim/ImageSeraph.cs index 5cc0c845..99a87887 100644 --- a/ArcFormats/Seraphim/ImageSeraph.cs +++ b/ArcFormats/Seraphim/ImageSeraph.cs @@ -124,6 +124,7 @@ namespace GameRes.Formats.Seraphim { // common case for 256-colors images Signatures = new uint[] { 0x01004243, 0 }; + Extensions = new string[] { "CB", "CLB" }; } public override ImageMetaData ReadMetaData (IBinaryStream stream) @@ -133,18 +134,18 @@ namespace GameRes.Formats.Seraphim return null; int colors = header.ToUInt16 (2); int packed_size = header.ToInt32 (12); - if (packed_size <= 0 || packed_size > stream.Length-0x10) + if (packed_size <= 0 /*|| packed_size > stream.Length-0x10*/) return null; - uint width = header.ToUInt16 (8); - uint height = header.ToUInt16 (10); - if (0 == width || 0 == height) + int width = header.ToInt16 (8); + int height = header.ToInt16 (10); + if (width <= 0 || height <= 0 || colors > 0x100) return null; return new SeraphMetaData { OffsetX = header.ToInt16 (4), OffsetY = header.ToInt16 (6), - Width = width, - Height = height, + Width = (uint)width, + Height = (uint)height, BPP = 8, PackedSize = packed_size, Colors = colors, @@ -363,7 +364,7 @@ namespace GameRes.Formats.Seraphim private byte[] UnpackBytes () // sub_403ED0 { int total = m_width * m_height; - var output = new byte[total]; + var output = new byte[total + m_width]; int dst = 0; while ( dst < total ) { @@ -450,9 +451,9 @@ namespace GameRes.Formats.Seraphim } else { - int v36 = m_input.ReadByte() | ((next & 0xF) << 8); + int offset = m_input.ReadByte() | ((next & 0xF) << 8); count = m_input.ReadByte() + 1; - int src = dst - 1 - v36; + int src = dst - 1 - offset; Binary.CopyOverlapped (output, src, dst, count); } dst += count; diff --git a/ArcFormats/TechnoBrain/ArcIPQ.cs b/ArcFormats/TechnoBrain/ArcIPQ.cs new file mode 100644 index 00000000..8a4db8f9 --- /dev/null +++ b/ArcFormats/TechnoBrain/ArcIPQ.cs @@ -0,0 +1,116 @@ +//! \file ArcIPQ.cs +//! \date 2022 May 02 +//! \brief TechnoBrain's animation resource. +// +// Copyright (C) 2022 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.TechnoBrain +{ + internal class IpqArchive : ArcFile + { + public readonly IpfMetaData Info; + + public IpqArchive (ArcView arc, ArchiveFormat impl, ICollection dir, IpfMetaData info) + : base (arc, impl, dir) + { + Info = info; + } + } + + [Export(typeof(ArchiveFormat))] + public class IpqOpener : ArchiveFormat + { + public override string Tag { get { return "IPQ"; } } + public override string Description { get { return "TechnoBrain's animation resource"; } } + public override uint Signature { get { return 0; } } // 'RIFF' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + static readonly ResourceInstance Ipf = new ResourceInstance("IPF"); + + public override ArcFile TryOpen (ArcView file) + { + if (file.View.ReadUInt32 (0) != 0x46464952 // 'RIFF' + || !file.View.AsciiEqual (8, "IPQ fmt ")) + return null; + IpfMetaData ipq_info; + using (var ipq = file.CreateStream()) + { + ipq_info = Ipf.Value.ReadIpfHeader (ipq); + if (null == ipq_info || ipq_info.FormatString != "IPQ fmt ") + return null; + ipq.Position = ipq_info.DataOffset; + if (ipq.ReadUInt32() != 0x6D696E61) // "anim" + return null; + uint index_size = ipq.ReadUInt32(); + int count = ipq.ReadInt32(); + if (!IsSaneCount (count)) + return null; + + var base_name = Path.GetFileNameWithoutExtension (file.Name); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new Entry { + Name = string.Format ("{0}#{1:D3}", base_name, i), + Type = "image", + Offset = ipq.ReadUInt32(), + }; + dir.Add (entry); + } + long last_offset = file.MaxOffset; + for (int i = count-1; i >= 0; --i) + { + dir[i].Size = (uint)(last_offset - dir[i].Offset); + last_offset = dir[i].Offset; + } + return new IpqArchive (file, this, dir, ipq_info); + } + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var ipq = arc as IpqArchive; + if (null == ipq) + return base.OpenImage (arc, entry); + var info = ipq.Info.Clone() as IpfMetaData; + var file = arc.File.CreateStream(); + try + { + file.Position = entry.Offset; + if (!Ipf.Value.ReadBmpInfo (file, info)) + throw new InvalidFormatException ("Invalid 'bmp' section."); + return new IpfReader (file, info, Ipf.Value); + } + catch + { + file.Dispose(); + throw; + } + } + } +} diff --git a/ArcFormats/TechnoBrain/ImageIPF.cs b/ArcFormats/TechnoBrain/ImageIPF.cs index 2ac78b20..d0cb670f 100644 --- a/ArcFormats/TechnoBrain/ImageIPF.cs +++ b/ArcFormats/TechnoBrain/ImageIPF.cs @@ -23,6 +23,7 @@ // IN THE SOFTWARE. // +using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; @@ -31,13 +32,21 @@ using GameRes.Utility; namespace GameRes.Formats.TechnoBrain { - internal class IpfMetaData : ImageMetaData + internal class IpfMetaData : ImageMetaData, ICloneable { public bool HasPalette; + public bool HasBitmap; public bool IsCompressed; public long PalOffset; public int PalSize; public long BmpOffset; + public long DataOffset; + public string FormatString; + + public object Clone () + { + return MemberwiseClone(); + } } [Export(typeof(ImageFormat))] @@ -47,24 +56,25 @@ namespace GameRes.Formats.TechnoBrain public override string Description { get { return "TechnoBrain's 'Inteligent Picture Format'"; } } public override uint Signature { get { return 0; } } // 'RIFF' - public override ImageMetaData ReadMetaData (IBinaryStream file) + internal IpfMetaData ReadIpfHeader (IBinaryStream file) { // 'RIFF' isn't included into signature to avoid auto-detection of the WAV files as IPF images. if (0x46464952 != file.Signature) // 'RIFF' return null; var header = file.ReadHeader (0x14); - if (!header.AsciiEqual (8, "IPF fmt ")) + if (!header.AsciiEqual (0xC, "fmt ")) return null; int fmt_size = header.ToInt32 (0x10); if (fmt_size < 0x24) return null; header = file.ReadHeader (0x14 + fmt_size); - bool has_palette = header.ToInt32 (0x18) != 0; - bool has_bitmap = header.ToInt32 (0x28) != 0; - if (!has_bitmap) - return null; - var info = new IpfMetaData { BPP = 8, HasPalette = has_palette }; - if (has_palette) + var info = new IpfMetaData { + BPP = 8, + HasPalette = header.ToInt32 (0x18) != 0, + HasBitmap = header.ToInt32 (0x28) != 0, + FormatString = header.GetCString (8, 8), + }; + if (info.HasPalette) { if (0x206C6170 != file.ReadInt32()) // 'pal ' return null; @@ -74,24 +84,43 @@ namespace GameRes.Formats.TechnoBrain info.PalOffset = file.Position; file.Position = info.PalOffset + info.PalSize; } + info.DataOffset = file.Position; + return info; + } + + internal bool ReadBmpInfo (IBinaryStream file, IpfMetaData info) + { if (0x20706D62 != file.ReadInt32()) // 'bmp ' - return null; + return false; int bmp_size = file.ReadInt32(); if (bmp_size <= 0x20) - return null; + return false; info.BmpOffset = file.Position + 0x18; info.Width = file.ReadUInt16(); info.Height = file.ReadUInt16(); - file.Seek (0xE, SeekOrigin.Current); + file.ReadUInt32(); + info.OffsetX = file.ReadInt16(); + info.OffsetY = file.ReadInt16(); + file.Seek (6, SeekOrigin.Current); info.IsCompressed = 0 != (file.ReadByte() & 1); + return true; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var info = ReadIpfHeader (file); + if (null == info || info.FormatString != "IPF fmt " || !info.HasBitmap) + return null; + file.Position = info.DataOffset; + if (!ReadBmpInfo (file, info)) + return null; return info; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var reader = new IpfReader (file, (IpfMetaData)info); - var pixels = reader.Unpack(); - return ImageData.Create (info, reader.Format, reader.Palette, pixels); + var reader = new IpfReader (file, (IpfMetaData)info, this); + return reader.Image; } public override void Write (Stream file, ImageData image) @@ -100,22 +129,34 @@ namespace GameRes.Formats.TechnoBrain } } - internal sealed class IpfReader + internal sealed class IpfReader : IImageDecoder { IBinaryStream m_input; IpfMetaData m_info; byte[] m_output; + ImageData m_image; public BitmapPalette Palette { get; private set; } public PixelFormat Format { get; private set; } - public byte[] Data { get { return m_output; } } - public IpfReader (IBinaryStream input, IpfMetaData info) + public Stream Source { get { return m_input.AsStream; } } + public ImageMetaData Info { get { return m_info; } } + public ImageData Image { get { return m_image ?? (m_image = GetImageData()); } } + public ImageFormat SourceFormat { get; private set; } + + public IpfReader (IBinaryStream input, IpfMetaData info, ImageFormat impl) { m_input = input; m_info = info; m_output = new byte[m_info.Width*m_info.Height]; Format = m_info.HasPalette ? PixelFormats.Indexed8 : PixelFormats.Gray8; + SourceFormat = impl; + } + + private ImageData GetImageData () + { + var pixels = Unpack(); + return ImageData.Create (m_info, Format, Palette, pixels); } public byte[] Unpack () @@ -151,7 +192,7 @@ namespace GameRes.Formats.TechnoBrain for (int j = 0; j < 8; ++j) { int dst = (i << 3) + j; - if (dst >= 0x0A && 0 != (bits & 0x80)) + if (dst >= min_index && 0 != (bits & 0x80)) { if (dst >= min_index && dst <= max_index) { @@ -199,5 +240,18 @@ namespace GameRes.Formats.TechnoBrain } } } + + #region IDisposable members + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_input.Dispose(); + m_disposed = true; + } + GC.SuppressFinalize (this); + } + #endregion } } diff --git a/ArcFormats/Unity/ArcASSET.cs b/ArcFormats/Unity/ArcASSET.cs index 37d53e69..f6e2444b 100644 --- a/ArcFormats/Unity/ArcASSET.cs +++ b/ArcFormats/Unity/ArcASSET.cs @@ -42,12 +42,19 @@ namespace GameRes.Formats.Unity public override ArcFile TryOpen (ArcView file) { uint header_size = Binary.BigEndian (file.View.ReadUInt32 (0)); - uint file_size = Binary.BigEndian (file.View.ReadUInt32 (4)); - if (file_size != file.MaxOffset || header_size > file_size || 0 == header_size) - return null; + long file_size = Binary.BigEndian (file.View.ReadUInt32 (4)); int format = Binary.BigEndian (file.View.ReadInt32 (8)); - uint data_offset = Binary.BigEndian (file.View.ReadUInt32 (12)); - if (format <= 0 || format > 0x100 || data_offset >= file_size || data_offset < header_size) + if (format <= 0 || format > 0x100) + return null; + long data_offset = Binary.BigEndian (file.View.ReadUInt32 (12)); + if (format >= 22) + { + header_size = Binary.BigEndian (file.View.ReadUInt32 (0x14)); + file_size = Binary.BigEndian (file.View.ReadInt64 (0x18)); + data_offset = Binary.BigEndian (file.View.ReadInt64 (0x20)); + } + if (file_size != file.MaxOffset || header_size > file_size || 0 == header_size + || data_offset >= file_size || data_offset < header_size) return null; using (var stream = file.CreateStream()) using (var input = new AssetReader (stream)) @@ -84,7 +91,7 @@ namespace GameRes.Formats.Unity { reader.SetupReaders (obj.Asset); var tex = new Texture2D(); - tex.Load (reader, obj.Asset.Tree.Version); + tex.Load (reader, obj.Asset.Tree); if (0 == tex.m_DataLength) { reader.Dispose(); diff --git a/ArcFormats/Unity/ArcDSM.cs b/ArcFormats/Unity/ArcDSM.cs new file mode 100644 index 00000000..c8357219 --- /dev/null +++ b/ArcFormats/Unity/ArcDSM.cs @@ -0,0 +1,98 @@ +//! \file ArcDSM.cs +//! \date 2022 Apr 30 +//! \brief Encrypted DSM script file as an archive. +// +// Copyright (C) 2022 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace GameRes.Formats.Unity +{ + [Export(typeof(ArchiveFormat))] + public class DsmOpener : ArchiveFormat + { + public override string Tag { get { return "DSM/UNITY"; } } + public override string Description { get { return "Unity engine encrypted script file"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + const string DefaultPassword = "pass"; + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "data.dsm") + || (file.View.ReadUInt32 (0) & 0xFFFFFF) != 0xBFBBEF) // UTF-8 BOM + return null; + + var dir = new List { + new Entry { + Name = "data.txt", + Type = "script", + Offset = 0, + Size = (uint)file.MaxOffset / 4 * 3, + } + }; + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + using (var input = arc.File.CreateStream()) + using (var reader = new StreamReader (input)) + { + var sourceString = reader.ReadToEnd(); + var text = DecryptString (sourceString, DefaultPassword); + return new BinMemoryStream (text, entry.Name); + } + } + + static byte[] DecryptString (string sourceString, string password) + { + var rijndaelManaged = new RijndaelManaged(); + byte[] key, iv; + GenerateKeyFromPassword (password, rijndaelManaged.KeySize, out key, rijndaelManaged.BlockSize, out iv); + rijndaelManaged.Key = key; + rijndaelManaged.IV = iv; + var array = Convert.FromBase64String (sourceString); + using (var cryptoTransform = rijndaelManaged.CreateDecryptor()) + { + return cryptoTransform.TransformFinalBlock (array, 0, array.Length); + } + } + + static readonly byte[] DefaultSalt = Encoding.UTF8.GetBytes("saltは必ず8バイト以上"); + + static void GenerateKeyFromPassword (string password, int keySize, out byte[] key, int blockSize, out byte[] iv) + { + var rfc2898DeriveBytes = new Rfc2898DeriveBytes (password, DefaultSalt); + rfc2898DeriveBytes.IterationCount = 1000; + key = rfc2898DeriveBytes.GetBytes (keySize / 8); + iv = rfc2898DeriveBytes.GetBytes (blockSize / 8); + } + } +} diff --git a/ArcFormats/Unity/ArcUnityFS.cs b/ArcFormats/Unity/ArcUnityFS.cs index 8b4c6bed..45a421d2 100644 --- a/ArcFormats/Unity/ArcUnityFS.cs +++ b/ArcFormats/Unity/ArcUnityFS.cs @@ -87,6 +87,7 @@ namespace GameRes.Formats.Unity case 1: index_data = UnpackLzma (packed, index_size); break; + case 2: case 3: index_data = new byte[index_size]; Lz4Compressor.DecompressBlock (packed, packed.Length, index_data, index_data.Length); diff --git a/ArcFormats/Unity/Asset.cs b/ArcFormats/Unity/Asset.cs index 078a57ae..f49db1a4 100644 --- a/ArcFormats/Unity/Asset.cs +++ b/ArcFormats/Unity/Asset.cs @@ -41,7 +41,7 @@ namespace GameRes.Formats.Unity internal class Asset { int m_format; - uint m_data_offset; + long m_data_offset; bool m_is_little_endian; UnityTypeData m_tree = new UnityTypeData(); Dictionary m_adds; @@ -63,6 +63,13 @@ namespace GameRes.Formats.Unity m_data_offset = input.ReadUInt32(); if (m_format >= 9) m_is_little_endian = 0 == input.ReadInt32(); + if (m_format >= 22) + { + input.ReadInt32(); // header_size + input.ReadInt64(); // file_size + m_data_offset = input.ReadInt64(); + input.ReadInt64(); + } input.SetupReaders (this); m_tree.Load (input); @@ -176,7 +183,8 @@ namespace GameRes.Formats.Unity public void Load (AssetReader reader) { PathId = reader.ReadId(); - Offset = reader.ReadUInt32() + Asset.DataOffset; + Offset = reader.ReadOffset(); + Offset += Asset.DataOffset; Size = reader.ReadUInt32(); if (Asset.Format < 17) { diff --git a/ArcFormats/Unity/AssetReader.cs b/ArcFormats/Unity/AssetReader.cs index ffedf5c5..c77b993e 100644 --- a/ArcFormats/Unity/AssetReader.cs +++ b/ArcFormats/Unity/AssetReader.cs @@ -65,6 +65,7 @@ namespace GameRes.Formats.Unity public Func ReadInt32; public Func ReadInt64; public Func ReadId; + public Func ReadOffset; public void SetupReaders (Asset asset) { @@ -109,6 +110,10 @@ namespace GameRes.Formats.Unity ReadId = ReadInt64; else ReadId = () => ReadInt32(); + if (m_format >= 22) + ReadOffset = ReadInt64; + else + ReadOffset = () => ReadUInt32(); } /// @@ -122,6 +127,11 @@ namespace GameRes.Formats.Unity ReadId = () => ReadInt32(); } + public void Skip (int count) + { + m_input.Seek (count, SeekOrigin.Current); + } + /// /// Read bytes into specified buffer. /// diff --git a/ArcFormats/Unity/AudioClip.cs b/ArcFormats/Unity/AudioClip.cs index 74fbf692..ccd7c090 100644 --- a/ArcFormats/Unity/AudioClip.cs +++ b/ArcFormats/Unity/AudioClip.cs @@ -97,13 +97,13 @@ namespace GameRes.Formats.Unity internal class StreamingInfo { - public uint Offset; + public long Offset; public uint Size; public string Path; public void Load (AssetReader reader) { - Offset = reader.ReadUInt32(); + Offset = reader.ReadOffset(); Size = reader.ReadUInt32(); Path = reader.ReadString(); } diff --git a/ArcFormats/Unity/Bc7Decoder.cs b/ArcFormats/Unity/Bc7Decoder.cs new file mode 100644 index 00000000..1a4cee37 --- /dev/null +++ b/ArcFormats/Unity/Bc7Decoder.cs @@ -0,0 +1,571 @@ +//! \file Bc7Decoder.cs +//! \date 2022 May 03 +//! \brief BC7 texture compression decoder. +// +// Based on the [bc7enc](https://github.com/richgel999/bc7enc) +// +// Copyright(c) 2020 Richard Geldreich, Jr. +// +// C# port copyright (C) 2022 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 GameRes.Utility; + +namespace GameRes.Formats.Unity +{ + public class Bc7Decoder + { + byte[] m_input; + int m_width; + int m_height; + int m_output_stride; + byte[] m_output; + byte[] m_block = new byte[64]; + + public Bc7Decoder (byte[] input, ImageMetaData info) + { + m_input = input; + m_width = info.iWidth; + m_output_stride = m_width * 4; + m_height = info.iHeight; + m_output = new byte[m_output_stride*m_height]; + } + + public byte[] Unpack () + { + int block_step_y = m_output_stride * 4; + int block_step_x = 16; + int src = 0; + for (int y = 0; y < m_output.Length; y += block_step_y) + for (int x = 0; x < m_output_stride; x += block_step_x) + { + DecompressBc7Block (src); + int dst = y + x; + int block_src = 0; + for (int i = 0; i < 4; ++i) + { + int row_dst = dst; + for (int j = 0; j < 4; ++j) + { + m_output[row_dst++] = m_block[block_src+2]; + m_output[row_dst++] = m_block[block_src+1]; + m_output[row_dst++] = m_block[block_src ]; + m_output[row_dst++] = m_block[block_src+3]; + block_src += 4; + } + dst += m_output_stride; + } + src += 16; + } + return m_output; + } + + private bool DecompressBc7Block (int src) + { + byte first_byte = m_input[src]; + for (int mode = 0; mode < 8; ++mode) + { + if ((first_byte & (1u << mode)) != 0) + { + switch (mode) + { + case 0: + case 2: + return UnpackBc7Mode0_2 (mode, src); + case 1: + case 3: + case 7: + return UnpackBc7Mode1_3_7 (mode, src); + case 4: + case 5: + return UnpackBc7Mode4_5 (mode, src); + case 6: + return UnpackBc7Mode6 (src); + } + } + } + return false; + } + + int m_bit_offset; + + uint ReadBits32 (int src, int codesize) + { + uint bits = 0; + int total_bits = 0; + + while (total_bits < codesize) + { + int byte_bit_offset = m_bit_offset & 7; + int bits_to_read = Math.Min (codesize - total_bits, 8 - byte_bit_offset); + + uint byte_bits = (uint)m_input[src + (m_bit_offset >> 3)] >> byte_bit_offset; + byte_bits &= ((1u << bits_to_read) - 1u); + + bits |= (byte_bits << total_bits); + + total_bits += bits_to_read; + m_bit_offset += bits_to_read; + } + return bits; + } + + byte[] endpoints = new byte[8 * 4]; + uint[] pbits = new uint[6]; + uint[] weights = new uint[16]; + uint[] a_weights = new uint[16]; + int[] weight_bits = new int[2]; + byte[,] block_colors= new byte[3,32]; + + bool UnpackBc7Mode0_2 (int mode, int src) + { + const uint ENDPOINTS = 6; + const uint COMPS = 3; + int WEIGHT_BITS = (mode == 0) ? 3 : 2; + int ENDPOINT_BITS = (mode == 0) ? 4 : 5; + int PBITS = (mode == 0) ? 6 : 0; + uint WEIGHT_VALS = 1u << WEIGHT_BITS; + + m_bit_offset = 0; + + if (ReadBits32 (src, mode + 1) != (1u << mode)) + return false; + + uint part = ReadBits32 (src, (mode == 0) ? 4 : 6); + + for (uint c = 0; c < COMPS; c++) + for (uint e = 0; e < ENDPOINTS * 4; e += 4) + endpoints[e+c] = (byte)ReadBits32(src, ENDPOINT_BITS); + + for (uint p = 0; p < PBITS; p++) + pbits[p] = ReadBits32 (src, 1); + + for (uint i = 0; i < 16; i++) + weights[i] = ReadBits32 (src, ((i == 0) || (i == s_bc7_table_anchor_index_third_subset_1[part]) || (i == s_bc7_table_anchor_index_third_subset_2[part])) ? (WEIGHT_BITS - 1) : WEIGHT_BITS); + + for (uint e = 0; e < ENDPOINTS * 4; e += 4) + for (uint c = 0; c < 4; c++) + endpoints[e+c] = (byte)((c == 3) ? 0xFF : (PBITS != 0 ? bc7_dequant(endpoints[e+c], pbits[e/4], ENDPOINT_BITS) : bc7_dequant(endpoints[e+c], ENDPOINT_BITS))); + + for (uint s = 0; s < 3; s++) + for (uint i = 0; i < WEIGHT_VALS*4; i += 4) + { + for (uint c = 0; c < 3; c++) + block_colors[s,i+c] = (byte)bc7_interp(endpoints[s * 8 + c], endpoints[s * 8 + 4 + c], i/4, WEIGHT_BITS); + block_colors[s,i+3] = 0xFF; + } + + for (uint i = 0; i < 16*4; i += 4) + { + int b = s_bc7_partition3[part * 16 + i/4]; + uint c = weights[i/4] * 4; + m_block[i ] = block_colors[b,c]; + m_block[i+1] = block_colors[b,c+1]; + m_block[i+2] = block_colors[b,c+2]; + m_block[i+3] = block_colors[b,c+3]; + } + return true; + } + + bool UnpackBc7Mode1_3_7 (int mode, int src) + { + const uint ENDPOINTS = 4; + int COMPS = (mode == 7) ? 4 : 3; + int WEIGHT_BITS = (mode == 1) ? 3 : 2; + int ENDPOINT_BITS = (mode == 7) ? 5 : ((mode == 1) ? 6 : 7); + int PBITS = (mode == 1) ? 2 : 4; + bool SHARED_PBITS = mode == 1; + uint WEIGHT_VALS = 1u << WEIGHT_BITS; + + m_bit_offset = 0; + + if (ReadBits32 (src, mode + 1) != (1u << mode)) + return false; + + uint part = ReadBits32 (src, 6); + + for (uint c = 0; c < COMPS; c++) + for (uint e = 0; e < ENDPOINTS * 4; e += 4) + endpoints[e+c] = (byte)ReadBits32(src, ENDPOINT_BITS); + + for (uint p = 0; p < PBITS; p++) + pbits[p] = ReadBits32 (src, 1); + + for (uint i = 0; i < 16; i++) + weights[i] = ReadBits32(src, ((i == 0) || (i == s_bc7_table_anchor_index_second_subset[part])) ? (WEIGHT_BITS - 1) : WEIGHT_BITS); + + for (uint e = 0; e < ENDPOINTS*4; e += 4) + for (uint c = 0; c < 4; c++) + endpoints[e+c] = (byte)((c == ((mode == 7u) ? 4u : 3u)) ? 0xFF : bc7_dequant(endpoints[e+c], pbits[SHARED_PBITS ? ((e/4) >> 1) : (e/4)], ENDPOINT_BITS)); + + for (uint s = 0; s < 2; s++) + for (uint i = 0; i < WEIGHT_VALS*4; i += 4) + { + for (uint c = 0; c < COMPS; c++) + block_colors[s,i+c] = (byte)bc7_interp(endpoints[(s * 2)*4+c], endpoints[(s * 2 + 1)*4+c], i/4, WEIGHT_BITS); + block_colors[s,i+3] = (COMPS == 3) ? (byte)0xFF : block_colors[s,i+3]; + } + + for (uint i = 0; i < 16*4; i += 4) + { + int b = s_bc7_partition2[part * 16 + i/4]; + uint c = weights[i/4] * 4; + m_block[i ] = block_colors[b,c]; + m_block[i+1] = block_colors[b,c+1]; + m_block[i+2] = block_colors[b,c+2]; + m_block[i+3] = block_colors[b,c+3]; + } + return true; + } + + bool UnpackBc7Mode4_5 (int mode, int src) + { + const uint ENDPOINTS = 2; + const uint COMPS = 4; + const int WEIGHT_BITS = 2; + int A_WEIGHT_BITS = (mode == 4) ? 3 : 2; + int ENDPOINT_BITS = (mode == 4) ? 5 : 7; + int A_ENDPOINT_BITS = (mode == 4) ? 6 : 8; + + m_bit_offset = 0; + + if (ReadBits32(src, mode + 1) != (1u << mode)) + return false; + + uint comp_rot = ReadBits32 (src, 2); + uint index_mode = (mode == 4) ? ReadBits32 (src, 1) : 0; + + for (uint c = 0; c < COMPS; c++) + for (uint e = 0; e < ENDPOINTS*4; e += 4) + endpoints[e+c] = (byte)ReadBits32(src, (c == 3) ? A_ENDPOINT_BITS : ENDPOINT_BITS); + + weight_bits[0] = index_mode != 0 ? A_WEIGHT_BITS : WEIGHT_BITS; + weight_bits[1] = index_mode != 0 ? WEIGHT_BITS : A_WEIGHT_BITS; + + uint[] w_array = index_mode != 0 ? a_weights : weights; + for (uint i = 0; i < 16; i++) + w_array[i] = ReadBits32 (src, weight_bits[index_mode] - ((i == 0) ? 1 : 0)); + + w_array = index_mode != 0 ? weights : a_weights; + for (uint i = 0; i < 16; i++) + w_array[i] = ReadBits32 (src, weight_bits[1 - index_mode] - ((i == 0) ? 1 : 0)); + + for (uint e = 0; e < ENDPOINTS*4; e += 4) + for (uint c = 0; c < 4; c++) + endpoints[e+c] = (byte)bc7_dequant(endpoints[e+c], (c == 3) ? A_ENDPOINT_BITS : ENDPOINT_BITS); + + for (uint i = 0; i < (1U << weight_bits[0]) * 4; i += 4) + for (uint c = 0; c < 3; c++) + block_colors[0,i+c] = (byte)bc7_interp(endpoints[c], endpoints[4+c], i/4, weight_bits[0]); + + for (uint i = 0; i < (1U << weight_bits[1]) * 4; i += 4) + block_colors[0,i+3] = (byte)bc7_interp(endpoints[3], endpoints[4+3], i/4, weight_bits[1]); + + for (uint i = 0; i < 16*4; i += 4) + { + uint w = weights[i / 4] * 4; + m_block[i ] = block_colors[0,w]; + m_block[i+1] = block_colors[0,w+1]; + m_block[i+2] = block_colors[0,w+2]; + m_block[i+3] = block_colors[0,a_weights[i/4]*4+3]; + if (comp_rot >= 1) + { + byte a = m_block[i+3]; + m_block[i+3] = m_block[i+comp_rot-1]; + m_block[i+comp_rot-1] = a; + } + } + return true; + } + + internal class Bc7Mode_6 + { + public struct Lo + { + public byte mode ; //: 7; + public byte r0 ; //: 7; + public byte r1 ; //: 7; + public byte g0 ; //: 7; + public byte g1 ; //: 7; + public byte b0 ; //: 7; + public byte b1 ; //: 7; + public byte a0 ; //: 7; + public byte a1 ; //: 7; + public byte p0 ; //: 1; + } + public struct Hi + { + public byte p1 ; //: 1; + public byte s00 ; //: 3; + public byte s10 ; //: 4; + public byte s20 ; //: 4; + public byte s30 ; //: 4; + + public byte s01 ; //: 4; + public byte s11 ; //: 4; + public byte s21 ; //: 4; + public byte s31 ; //: 4; + + public byte s02 ; //: 4; + public byte s12 ; //: 4; + public byte s22 ; //: 4; + public byte s32 ; //: 4; + + public byte s03 ; //: 4; + public byte s13 ; //: 4; + public byte s23 ; //: 4; + public byte s33 ; //: 4; + } + public Lo m_lo; + public Hi m_hi; + + public void Unpack (byte[] input, int src) + { + ulong lo_bits = input.ToUInt64 (src); + ulong hi_bits = input.ToUInt64 (src+8); + m_lo.mode = (byte)( lo_bits & 0x7F); + m_lo.r0 = (byte)((lo_bits >> 7) & 0x7F); + m_lo.r1 = (byte)((lo_bits >> 14) & 0x7F); + m_lo.g0 = (byte)((lo_bits >> 21) & 0x7F); + m_lo.g1 = (byte)((lo_bits >> 28) & 0x7F); + m_lo.b0 = (byte)((lo_bits >> 35) & 0x7F); + m_lo.b1 = (byte)((lo_bits >> 42) & 0x7F); + m_lo.a0 = (byte)((lo_bits >> 49) & 0x7F); + m_lo.a1 = (byte)((lo_bits >> 56) & 0x7F); + m_lo.p0 = (byte)((lo_bits >> 63)); + + m_hi.p1 = (byte)((hi_bits & 1)); + m_hi.s00 = (byte)((hi_bits >> 1) & 0x7); + m_hi.s10 = (byte)((hi_bits >> 4) & 0xF); + m_hi.s20 = (byte)((hi_bits >> 8) & 0xF); + m_hi.s30 = (byte)((hi_bits >> 12) & 0xF); + m_hi.s01 = (byte)((hi_bits >> 16) & 0xF); + m_hi.s11 = (byte)((hi_bits >> 20) & 0xF); + m_hi.s21 = (byte)((hi_bits >> 24) & 0xF); + m_hi.s31 = (byte)((hi_bits >> 28) & 0xF); + m_hi.s02 = (byte)((hi_bits >> 32) & 0xF); + m_hi.s12 = (byte)((hi_bits >> 36) & 0xF); + m_hi.s22 = (byte)((hi_bits >> 40) & 0xF); + m_hi.s32 = (byte)((hi_bits >> 44) & 0xF); + m_hi.s03 = (byte)((hi_bits >> 48) & 0xF); + m_hi.s13 = (byte)((hi_bits >> 52) & 0xF); + m_hi.s23 = (byte)((hi_bits >> 56) & 0xF); + m_hi.s33 = (byte)((hi_bits >> 60)); + } + } + + Bc7Mode_6 mode_6_block = new Bc7Mode_6(); + uint[] vals = new uint[16]; + + bool UnpackBc7Mode6(int src) + { + mode_6_block.Unpack (m_input, src); + var block = mode_6_block; + + if (block.m_lo.mode != (1 << 6)) + return false; + + uint r0 = (uint)((block.m_lo.r0 << 1) | block.m_lo.p0); + uint g0 = (uint)((block.m_lo.g0 << 1) | block.m_lo.p0); + uint b0 = (uint)((block.m_lo.b0 << 1) | block.m_lo.p0); + uint a0 = (uint)((block.m_lo.a0 << 1) | block.m_lo.p0); + uint r1 = (uint)((block.m_lo.r1 << 1) | block.m_hi.p1); + uint g1 = (uint)((block.m_lo.g1 << 1) | block.m_hi.p1); + uint b1 = (uint)((block.m_lo.b1 << 1) | block.m_hi.p1); + uint a1 = (uint)((block.m_lo.a1 << 1) | block.m_hi.p1); + + for (int i = 0; i < 16; i++) + { + uint w = s_bc7_weights4[i]; + uint iw = 64 - w; + SetNoclampRgba(vals, i, + (r0 * iw + r1 * w + 32u) >> 6, + (g0 * iw + g1 * w + 32u) >> 6, + (b0 * iw + b1 * w + 32u) >> 6, + (a0 * iw + a1 * w + 32u) >> 6); + } + + LittleEndian.Pack (vals[block.m_hi.s00], m_block, 0); + LittleEndian.Pack (vals[block.m_hi.s10], m_block, 4); + LittleEndian.Pack (vals[block.m_hi.s20], m_block, 8); + LittleEndian.Pack (vals[block.m_hi.s30], m_block, 12); + + LittleEndian.Pack (vals[block.m_hi.s01], m_block, 16); + LittleEndian.Pack (vals[block.m_hi.s11], m_block, 20); + LittleEndian.Pack (vals[block.m_hi.s21], m_block, 24); + LittleEndian.Pack (vals[block.m_hi.s31], m_block, 28); + + LittleEndian.Pack (vals[block.m_hi.s02], m_block, 32); + LittleEndian.Pack (vals[block.m_hi.s12], m_block, 36); + LittleEndian.Pack (vals[block.m_hi.s22], m_block, 40); + LittleEndian.Pack (vals[block.m_hi.s32], m_block, 44); + + LittleEndian.Pack (vals[block.m_hi.s03], m_block, 48); + LittleEndian.Pack (vals[block.m_hi.s13], m_block, 52); + LittleEndian.Pack (vals[block.m_hi.s23], m_block, 56); + LittleEndian.Pack (vals[block.m_hi.s33], m_block, 60); + + return true; + } + + static void SetNoclampRgba (uint[] vals, int dst, uint sr, uint sg, uint sb, uint sa) + { + vals[dst] = (sr & 0xFF) | ((sg & 0xFF) << 8) | ((sb & 0xFF) << 16) | ((sa & 0xFF) << 24); + } + + static uint bc7_dequant (uint val, uint pbit, int val_bits) + { + int total_bits = val_bits + 1; + val = (val << 1) | pbit; + val <<= (8 - total_bits); + val |= (val >> total_bits); + return val; + } + + static uint bc7_dequant (uint val, int val_bits) + { + val <<= (8 - val_bits); + val |= (val >> val_bits); + return val; + } + + static uint bc7_interp2 (uint l, uint h, uint w) + { + return (l * (64 - s_bc7_weights2[w]) + h * s_bc7_weights2[w] + 32) >> 6; + } + + static uint bc7_interp3 (uint l, uint h, uint w) + { + return (l * (64 - s_bc7_weights3[w]) + h * s_bc7_weights3[w] + 32) >> 6; + } + + static uint bc7_interp4 (uint l, uint h, uint w) + { + return (l * (64 - s_bc7_weights4[w]) + h * s_bc7_weights4[w] + 32) >> 6; + } + + static uint bc7_interp (uint l, uint h, uint w, int bits) + { + switch (bits) + { + case 2: return bc7_interp2 (l, h, w); + case 3: return bc7_interp3 (l, h, w); + case 4: return bc7_interp4 (l, h, w); + default: return 0; + } + } + + static readonly uint[] s_bc7_weights2 = { 0, 21, 43, 64 }; + static readonly uint[] s_bc7_weights3 = { 0, 9, 18, 27, 37, 46, 55, 64 }; + static readonly uint[] s_bc7_weights4 = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; + + static readonly byte[] s_bc7_partition2 = { + 0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1, 0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1, + 0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1, 0,0,0,1,0,0,1,1,0,0,1,1,0,1,1,1, + 0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1, + 0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1, + 0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1, + 0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1, + 0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, + 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1, + 0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,1, 0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,0, 0,1,1,1,0,0,1,1,0,0,0,1,0,0,0,0, + 0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,1,0,0,0,1,1,0,0,1,1,1,0, + 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1, + 0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0, 0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0, + 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0, + 0,0,0,1,0,1,1,1,1,1,1,0,1,0,0,0, 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0, + 0,1,1,1,0,0,0,1,1,0,0,0,1,1,1,0, 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0, + 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1, 0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1, + 0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0, 0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0, + 0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0, 0,1,0,1,0,1,0,1,1,0,1,0,1,0,1,0, + 0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1, 0,1,0,1,1,0,1,0,1,0,1,0,0,1,0,1, + 0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,0, 0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0, + 0,0,1,1,0,0,1,0,0,1,0,0,1,1,0,0, 0,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0, + 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, 0,0,1,1,1,1,0,0,1,1,0,0,0,0,1,1, + 0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1, 0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0, + 0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,0, 0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0, + 0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0, 0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,0, + 0,1,1,0,1,1,0,0,1,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1, + 0,1,1,0,0,0,1,1,1,0,0,1,1,1,0,0, 0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0, + 0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,0,0,1,1,1,0,0,1, + 0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1, 0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,1, + 0,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1, 0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0, + 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0, 0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1 + }; + + static readonly byte[] s_bc7_partition3 = { + 0,0,1,1,0,0,1,1,0,2,2,1,2,2,2,2, 0,0,0,1,0,0,1,1,2,2,1,1,2,2,2,1, + 0,0,0,0,2,0,0,1,2,2,1,1,2,2,1,1, 0,2,2,2,0,0,2,2,0,0,1,1,0,1,1,1, + 0,0,0,0,0,0,0,0,1,1,2,2,1,1,2,2, 0,0,1,1,0,0,1,1,0,0,2,2,0,0,2,2, + 0,0,2,2,0,0,2,2,1,1,1,1,1,1,1,1, 0,0,1,1,0,0,1,1,2,2,1,1,2,2,1,1, + 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2, + 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2, 0,0,1,2,0,0,1,2,0,0,1,2,0,0,1,2, + 0,1,1,2,0,1,1,2,0,1,1,2,0,1,1,2, 0,1,2,2,0,1,2,2,0,1,2,2,0,1,2,2, + 0,0,1,1,0,1,1,2,1,1,2,2,1,2,2,2, 0,0,1,1,2,0,0,1,2,2,0,0,2,2,2,0, + 0,0,0,1,0,0,1,1,0,1,1,2,1,1,2,2, 0,1,1,1,0,0,1,1,2,0,0,1,2,2,0,0, + 0,0,0,0,1,1,2,2,1,1,2,2,1,1,2,2, 0,0,2,2,0,0,2,2,0,0,2,2,1,1,1,1, + 0,1,1,1,0,1,1,1,0,2,2,2,0,2,2,2, 0,0,0,1,0,0,0,1,2,2,2,1,2,2,2,1, + 0,0,0,0,0,0,1,1,0,1,2,2,0,1,2,2, 0,0,0,0,1,1,0,0,2,2,1,0,2,2,1,0, + 0,1,2,2,0,1,2,2,0,0,1,1,0,0,0,0, 0,0,1,2,0,0,1,2,1,1,2,2,2,2,2,2, + 0,1,1,0,1,2,2,1,1,2,2,1,0,1,1,0, 0,0,0,0,0,1,1,0,1,2,2,1,1,2,2,1, + 0,0,2,2,1,1,0,2,1,1,0,2,0,0,2,2, 0,1,1,0,0,1,1,0,2,0,0,2,2,2,2,2, + 0,0,1,1,0,1,2,2,0,1,2,2,0,0,1,1, 0,0,0,0,2,0,0,0,2,2,1,1,2,2,2,1, + 0,0,0,0,0,0,0,2,1,1,2,2,1,2,2,2, 0,2,2,2,0,0,2,2,0,0,1,2,0,0,1,1, + 0,0,1,1,0,0,1,2,0,0,2,2,0,2,2,2, 0,1,2,0,0,1,2,0,0,1,2,0,0,1,2,0, + 0,0,0,0,1,1,1,1,2,2,2,2,0,0,0,0, 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0, + 0,1,2,0,2,0,1,2,1,2,0,1,0,1,2,0, 0,0,1,1,2,2,0,0,1,1,2,2,0,0,1,1, + 0,0,1,1,1,1,2,2,2,2,0,0,0,0,1,1, 0,1,0,1,0,1,0,1,2,2,2,2,2,2,2,2, + 0,0,0,0,0,0,0,0,2,1,2,1,2,1,2,1, 0,0,2,2,1,1,2,2,0,0,2,2,1,1,2,2, + 0,0,2,2,0,0,1,1,0,0,2,2,0,0,1,1, 0,2,2,0,1,2,2,1,0,2,2,0,1,2,2,1, + 0,1,0,1,2,2,2,2,2,2,2,2,0,1,0,1, 0,0,0,0,2,1,2,1,2,1,2,1,2,1,2,1, + 0,1,0,1,0,1,0,1,0,1,0,1,2,2,2,2, 0,2,2,2,0,1,1,1,0,2,2,2,0,1,1,1, + 0,0,0,2,1,1,1,2,0,0,0,2,1,1,1,2, 0,0,0,0,2,1,1,2,2,1,1,2,2,1,1,2, + 0,2,2,2,0,1,1,1,0,1,1,1,0,2,2,2, 0,0,0,2,1,1,1,2,1,1,1,2,0,0,0,2, + 0,1,1,0,0,1,1,0,0,1,1,0,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,1,2,2,1,1,2, + 0,1,1,0,0,1,1,0,2,2,2,2,2,2,2,2, 0,0,2,2,0,0,1,1,0,0,1,1,0,0,2,2, + 0,0,2,2,1,1,2,2,1,1,2,2,0,0,2,2, 0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,2, + 0,0,0,2,0,0,0,1,0,0,0,2,0,0,0,1, 0,2,2,2,1,2,2,2,0,2,2,2,1,2,2,2, + 0,1,0,1,2,2,2,2,2,2,2,2,2,2,2,2, 0,1,1,1,2,0,1,1,2,2,0,1,2,2,2,0, + }; + + static readonly byte[] s_bc7_table_anchor_index_second_subset = { + 15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15, + 15, 2, 8, 2, 2, 8, 8,15, 2, 8, 2, 2, 8, 8, 2, 2, + 15,15, 6, 8, 2, 8,15,15, 2, 8, 2, 2, 2,15,15, 6, + 6, 2, 6, 8,15,15, 2, 2, 15,15,15,15,15, 2, 2,15 + }; + + static readonly byte[] s_bc7_table_anchor_index_third_subset_1 = { + 3, 3,15,15, 8, 3,15,15, 8, 8, 6, 6, 6, 5, 3, 3, + 3, 3, 8,15, 3, 3, 6,10, 5, 8, 8, 6, 8, 5,15,15, + 8,15, 3, 5, 6,10, 8,15, 15, 3,15, 5,15,15,15,15, + 3,15, 5, 5, 5, 8, 5,10, 5,10, 8,13,15,12, 3, 3 + }; + + static readonly byte[] s_bc7_table_anchor_index_third_subset_2 = { + 15, 8, 8, 3,15,15, 3, 8, 15,15,15,15,15,15,15, 8, + 15, 8,15, 3,15, 8,15, 8, 3,15, 6,10,15,15,10, 8, + 15, 3,15,10,10, 8, 9,10, 6,15, 8,15, 3, 6, 6, 8, + 15, 3,15,15,15,15,15,15, 15,15,15,15, 3,15,15, 8 + }; + } +} diff --git a/ArcFormats/Unity/BundleStream.cs b/ArcFormats/Unity/BundleStream.cs index 4483d699..fae4ff7e 100644 --- a/ArcFormats/Unity/BundleStream.cs +++ b/ArcFormats/Unity/BundleStream.cs @@ -118,7 +118,7 @@ namespace GameRes.Formats.Unity m_packed = new byte[segment.PackedSize]; int packed_size = m_input.Read (m_packed, 0, (int)segment.PackedSize); var output = PrepareBuffer (segment.UnpackedSize); - if (3 == method) + if (3 == method || 2 == method) m_buffer_len = Lz4Compressor.DecompressBlock (m_packed, packed_size, output, (int)segment.UnpackedSize); else throw new NotImplementedException ("Not supported Unity asset bundle compression."); diff --git a/ArcFormats/Unity/OggStream.cs b/ArcFormats/Unity/OggStream.cs index 37309a04..6aabcd3a 100644 --- a/ArcFormats/Unity/OggStream.cs +++ b/ArcFormats/Unity/OggStream.cs @@ -208,7 +208,7 @@ namespace GameRes.Formats.Vorbis GranuleVals[LacingFill + i] = GranulePos; } LacingVals[LacingFill + i] = bytes % 0xFF; - GranulePos = GranuleVals[LacingFill+i] = GranulePos; + GranulePos = GranuleVals[LacingFill+i] = op.GranulePos; // flag the first segment as the beginning of the packet LacingVals[LacingFill] |= 0x100; diff --git a/ArcFormats/Unity/ResourcesAssets.cs b/ArcFormats/Unity/ResourcesAssets.cs index 612b49f5..83188d06 100644 --- a/ArcFormats/Unity/ResourcesAssets.cs +++ b/ArcFormats/Unity/ResourcesAssets.cs @@ -61,7 +61,7 @@ namespace GameRes.Formats.Unity case 28: // Texture2D { var tex = new Texture2D(); - tex.Load (input, asset.Tree.Version); + tex.Load (input, asset.Tree); if (0 == tex.m_DataLength) { var stream_data = new StreamingInfo(); diff --git a/ArcFormats/Unity/Texture2D.cs b/ArcFormats/Unity/Texture2D.cs index 2dd710f4..7382a1cf 100644 --- a/ArcFormats/Unity/Texture2D.cs +++ b/ArcFormats/Unity/Texture2D.cs @@ -97,12 +97,17 @@ namespace GameRes.Formats.Unity m_DataLength = reader.ReadInt32(); } - public void Load (AssetReader reader, string version) + public void Load (AssetReader reader, UnityTypeData type) { - if (version != "2017.3.1f1") + if ("2021.1.3f1" == type.Version) // type.Hashes[28] == [0D 08 41 4C FD 5B DB 0D 22 79 20 11 BD A9 AB 26] + { + Load2021 (reader); + return; + } + if (type.Version != "2017.3.1f1") { Load (reader); - if (0 == m_DataLength && version.StartsWith ("2017.")) // "2017.2.0f3" || "2017.1.1p1" + if (0 == m_DataLength && type.Version.StartsWith ("2017.")) // "2017.2.0f3" || "2017.1.1p1" reader.ReadInt64(); return; } @@ -130,6 +135,35 @@ namespace GameRes.Formats.Unity m_DataLength = reader.ReadInt32(); } + public void Load2021 (AssetReader reader) + { + m_Name = reader.ReadString(); + reader.Align(); + reader.ReadInt32(); // m_ForcedFallbackFormat + reader.ReadInt32(); // m_DownscaleFallback + m_Width = reader.ReadInt32(); + m_Height = reader.ReadInt32(); + m_CompleteImageSize = reader.ReadInt32(); + reader.ReadInt32(); // m_MipsStripped; + m_TextureFormat = (TextureFormat)reader.ReadInt32(); + m_MipCount = reader.ReadInt32(); + m_IsReadable = reader.ReadBool(); + reader.Align(); + reader.ReadInt32(); // m_StreamingMipmapsPriority + m_ImageCount = reader.ReadInt32(); + m_TextureDimension = reader.ReadInt32(); + m_FilterMode = reader.ReadInt32(); + m_Aniso = reader.ReadInt32(); + m_MipBias = reader.ReadFloat(); + m_WrapMode = reader.ReadInt32(); // m_WrapU + reader.ReadInt32(); // m_WrapV + reader.ReadInt32(); // m_WrapW + reader.ReadInt32(); // m_LightmapFormat + m_ColorSpace = reader.ReadInt32(); + reader.ReadInt32(); + m_DataLength = reader.ReadInt32(); + } + public void LoadData (AssetReader reader) { m_Data = reader.ReadBytes (m_DataLength); @@ -243,6 +277,12 @@ namespace GameRes.Formats.Unity pixels = ConvertArgb16 (m_texture.m_Data); break; + case TextureFormat.BC7: + { + var decoder = new Bc7Decoder (m_texture.m_Data, Info); + pixels = decoder.Unpack(); + break; + } default: throw new NotImplementedException (string.Format ("Not supported Unity Texture2D format '{0}'.", m_texture.m_TextureFormat)); } diff --git a/ArcFormats/app.config b/ArcFormats/app.config index 1b59f64c..ca23fc6e 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -226,7 +226,7 @@ - + diff --git a/ArcFormats/packages.config b/ArcFormats/packages.config index d5d65c41..c9e8b0fb 100644 --- a/ArcFormats/packages.config +++ b/ArcFormats/packages.config @@ -1,14 +1,13 @@  - - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/Console/ConsoleBrowser.cs b/Console/ConsoleBrowser.cs index a9556571..5905b3f7 100644 --- a/Console/ConsoleBrowser.cs +++ b/Console/ConsoleBrowser.cs @@ -96,7 +96,7 @@ namespace GARbro return; } var tag = args[argn+1]; - m_image_format = FindFormat (tag); + m_image_format = ImageFormat.FindByTag (tag); if (null == m_image_format) { Console.Error.WriteLine ("{0}: unknown format specified", tag); @@ -132,7 +132,7 @@ namespace GARbro { VFS.ChDir (m_arc_name); } - catch (Exception X) + catch (Exception) { Console.Error.WriteLine ("{0}: unknown format", m_arc_name); continue; @@ -174,8 +174,9 @@ namespace GARbro static void Usage () { Console.WriteLine ("Usage: gameres [OPTIONS] ARC [ENTRIES]"); - Console.WriteLine (" -l list recognized archive formats"); - Console.WriteLine (" -x extract all files"); + Console.WriteLine (" -l list recognized archive formats"); + Console.WriteLine (" -x extract all files"); + Console.WriteLine (" -c FORMAT convert images to specified format"); Console.WriteLine ("Without options displays contents of specified archive."); } diff --git a/Experimental/Experimental.csproj b/Experimental/Experimental.csproj index 297177f6..848e0917 100644 --- a/Experimental/Experimental.csproj +++ b/Experimental/Experimental.csproj @@ -54,6 +54,7 @@ ..\packages\Concentus.Oggfile.1.0.4\lib\net45\Concentus.Oggfile.dll + True ..\packages\MSFTCompressionCab.1.0.0\lib\Microsoft.Deployment.Compression.dll @@ -68,6 +69,8 @@ ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + ..\packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll @@ -116,6 +119,23 @@ ..\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + + + ..\packages\System.Memory.4.5.4\lib\netstandard1.1\System.Memory.dll + + + ..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net46\System.Security.Cryptography.X509Certificates.dll @@ -128,6 +148,9 @@ ..\packages\System.Xml.ReaderWriter.4.3.1\lib\net46\System.Xml.ReaderWriter.dll + + ..\packages\ZstdNet.1.4.5\lib\net45\ZstdNet.dll + @@ -140,6 +163,7 @@ + @@ -188,6 +212,16 @@ + + + PreserveNewest + + + + + PreserveNewest + + ipt @@ -204,6 +238,15 @@ exit 0 $(ProjectDir)Artemis\IPT + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + @@ -224,6 +267,7 @@ exit 0 +