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
+