From 63972014d746bbee2cc2e62b7b71f2d6fe211527 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:02:06 +0400 Subject: [PATCH 01/38] (Hypatia): LPK archives and LSG images. --- ArcFormats/Hypatia/ArcLPC.cs | 61 ++++++++++++++++++++ ArcFormats/Hypatia/ArcLPK.cs | 78 +++++++++++++++++++++++++ ArcFormats/Hypatia/ImageLSG.cs | 100 +++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 ArcFormats/Hypatia/ArcLPC.cs create mode 100644 ArcFormats/Hypatia/ArcLPK.cs create mode 100644 ArcFormats/Hypatia/ImageLSG.cs 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); + } + } + } +} From 9e12b9fb9011245548bdd22aff0d6414e16539d9 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:05:53 +0400 Subject: [PATCH 02/38] (Legacy): Toyo GSX K4 images. --- Legacy/Gsx/ImageK4.cs | 75 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Legacy/Gsx/ImageK4.cs diff --git a/Legacy/Gsx/ImageK4.cs b/Legacy/Gsx/ImageK4.cs new file mode 100644 index 00000000..93c3dca8 --- /dev/null +++ b/Legacy/Gsx/ImageK4.cs @@ -0,0 +1,75 @@ +//! \file ImageK4.cs +//! \date 2019 Feb 07 +//! \brief Toyo GSX image format. +// +// 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; + +// [030825][Mirai] Hoshi no Oujo + +namespace GameRes.Formats.Gsx +{ + internal class K4MetaData : ImageMetaData + { + public bool HasAlpha; + public int FrameCount; + } + + [Export(typeof(ImageFormat))] + public class K4Format : ImageFormat + { + public override string Tag { get { return "K4"; } } + public override string Description { get { return "Toyo GSX image format"; } } + public override uint Signature { get { return 0x0201344B; } } // 'K4' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + if (!header.AsciiEqual ("K4")) + return null; + if (header[2] != 1 || header[3] != 2) + return null; + return new K4MetaData { + Width = header.ToUInt16 (4), + Height = header.ToUInt16 (6), + BPP = header[0xF], + HasAlpha = header[0xB] != 0, + FrameCount = header.ToUInt16 (0xC), + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var meta = (K4MetaData)info; + + return ImageData.Create (info, format, palette, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("K4Format.Write not implemented"); + } + } +} From 8dd6a2820d5d01b4d7bf479c22b80282ca85ef39 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:06:11 +0400 Subject: [PATCH 03/38] (Legacy): BeF ALP bitmap masks. --- Legacy/hmp/ImageALP.cs | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Legacy/hmp/ImageALP.cs diff --git a/Legacy/hmp/ImageALP.cs b/Legacy/hmp/ImageALP.cs new file mode 100644 index 00000000..cd4cfc70 --- /dev/null +++ b/Legacy/hmp/ImageALP.cs @@ -0,0 +1,60 @@ +//! \file ImageALP.cs +//! \date 2022 Apr 22 +//! \brief BeF bitmap mask +// +// 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; + +namespace GameRes.Formats.Hmp +{ + [Export(typeof(ImageFormat))] + [ExportMetadata("Priority", -1)] + public class AlpFormat : ImageFormat + { + public override string Tag { get { return "ALP/BeF"; } } + public override string Description { get { return "BeF bitmap mask format"; } } + public override uint Signature { get { return 0; } } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".alp") || file.Length != 0x25800) + return null; + return new ImageMetaData { Width = 320, Height = 480, BPP = 8 }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var pixels = file.ReadBytes (0x25800); + for (int i = 0; i < pixels.Length; ++i) + pixels[i] = (byte)(pixels[i] * 0xFF / 0x40); + return ImageData.Create (info, PixelFormats.Gray8, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("AlpFormat.Write not implemented"); + } + } +} From a6e9d6e178c63b227537aba061639a1602843813 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:07:02 +0400 Subject: [PATCH 04/38] (CGD): recognize "spiel100" images. --- Legacy/KApp/ImageCGD.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Legacy/KApp/ImageCGD.cs b/Legacy/KApp/ImageCGD.cs index 17f8e9eb..ee1ea1e1 100644 --- a/Legacy/KApp/ImageCGD.cs +++ b/Legacy/KApp/ImageCGD.cs @@ -39,10 +39,17 @@ namespace GameRes.Formats.KApp public override string Description { get { return "KApp compressed image format"; } } public override uint Signature { get { return 0x6F6F746B; } } // 'ktool210' + public CgdFormat () + { + Signatures = new uint[] { 0x6F6F746B, 0x65697073 }; + } + public override ImageMetaData ReadMetaData (IBinaryStream file) { var header = file.ReadHeader (0x18); - if (!header.AsciiEqual ("ktool210") || header.ToInt32 (8) != 1) + if (header.ToInt32 (8) != 1) + return null; + if (!header.AsciiEqual ("ktool210") && !header.AsciiEqual ("spiel100")) return null; uint offset = header.ToUInt32 (0x10) & 0x7FFFFFFF; return CgdMetaData.FromStream (file, offset); From 8f4c3f5e7dfdd15b5c20f67c64d898b882aa8e7a Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:07:51 +0400 Subject: [PATCH 05/38] (GDT): tweaked baseline image lookup. --- Legacy/Pochette/ImageGDT.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Legacy/Pochette/ImageGDT.cs b/Legacy/Pochette/ImageGDT.cs index f6533920..8d98f7d4 100644 --- a/Legacy/Pochette/ImageGDT.cs +++ b/Legacy/Pochette/ImageGDT.cs @@ -27,6 +27,7 @@ using System; using System.ComponentModel.Composition; using System.IO; using System.Windows; +using System.Windows.Media; using System.Windows.Media.Imaging; namespace GameRes.Formats.Pochette @@ -93,15 +94,21 @@ namespace GameRes.Formats.Pochette BitmapSource BlendBaseLine (BitmapSource overlay, GdtMetaData meta) { - string base_name = VFS.ChangeFileName (meta.FileName, meta.BaseLine+".gdt"); + string base_name = VFS.ChangeFileName (meta.FileName, meta.BaseLine); if (!VFS.FileExists (base_name)) - return overlay; + { + base_name += ".gdt"; + if (!VFS.FileExists (base_name)) + return overlay; + } using (var base_file = VFS.OpenBinaryStream (base_name)) { var base_info = ReadMetaData (base_file) as GdtMetaData; if (null == base_info) return overlay; var base_image = ReadBitmapSource (base_file, base_info); + if (base_image.Format.BitsPerPixel < 24) + base_image = new FormatConvertedBitmap (base_image, PixelFormats.Bgr32, null, 0); var canvas = new WriteableBitmap (base_image); int canvas_bpp = canvas.Format.BitsPerPixel; if (canvas_bpp != overlay.Format.BitsPerPixel) From f330b16110e691acd59b87e1f8009581c1fc5002 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:08:39 +0400 Subject: [PATCH 06/38] (Legacy): PNX obfuscated PNG images. --- Legacy/Zenos/ImagePNX.cs | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Legacy/Zenos/ImagePNX.cs diff --git a/Legacy/Zenos/ImagePNX.cs b/Legacy/Zenos/ImagePNX.cs new file mode 100644 index 00000000..b42628b7 --- /dev/null +++ b/Legacy/Zenos/ImagePNX.cs @@ -0,0 +1,64 @@ +//! \file ImagePNX.cs +//! \date 2022 Apr 14 +//! \brief Obfuscated PNG image. +// +// 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; + +namespace GameRes.Formats.Zenos +{ + [Export(typeof(ImageFormat))] + public class PnxFormat : PngFormat + { + public override string Tag { get { return "PNX/ZENOS"; } } + public override string Description { get { return "Obfuscated PNG image"; } } + public override uint Signature { get { return 0x584E5089; } } + public override bool CanWrite { get { return false; } } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + using (var input = DeobfuscateStream (file)) + return base.ReadMetaData (input); + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + using (var input = DeobfuscateStream (file)) + return base.Read (input, info); + } + + IBinaryStream DeobfuscateStream (IBinaryStream file) + { + var body = new StreamRegion (file.AsStream, 8, file.Length-8, true); + var png = new PrefixStream (HeaderBytes, body); + return new BinaryStream (png, file.Name); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("PnxFormat.Write not implemented"); + } + } +} From 4fe241276ff026df718810d02cbc77a3ab277ce5 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:09:26 +0400 Subject: [PATCH 07/38] (DeleteItemExec): catch -> finally --- GUI/MainWindow.xaml.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/GUI/MainWindow.xaml.cs b/GUI/MainWindow.xaml.cs index 31dfefa6..9f83bf1d 100644 --- a/GUI/MainWindow.xaml.cs +++ b/GUI/MainWindow.xaml.cs @@ -1136,10 +1136,9 @@ namespace GARbro.GUI GARbro.Shell.File.Delete (file_list); count = file_list.Count(); } - catch + finally { ResumeWatchDirectoryChanges(); - throw; } RefreshView(); SetStatusText (Localization.Format ("MsgDeletedItems", count)); From 307805c4ac1700a96cdb0d3e65fdc6e14fa3f467 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:14:16 +0400 Subject: [PATCH 08/38] (GUI): added image format tag to status message. --- GUI/ImagePreview.cs | 6 +++--- GUI/Strings/guiStrings.ja-JP.resx | 2 +- GUI/Strings/guiStrings.ko-KR.resx | 2 +- GUI/Strings/guiStrings.resx | 2 +- GUI/Strings/guiStrings.ru-RU.resx | 2 +- GUI/Strings/guiStrings.zh-Hans.resx | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/GUI/ImagePreview.cs b/GUI/ImagePreview.cs index a8302588..0c5b227a 100644 --- a/GUI/ImagePreview.cs +++ b/GUI/ImagePreview.cs @@ -240,7 +240,7 @@ namespace GARbro.GUI { using (var data = VFS.OpenImage (preview.Entry)) { - SetPreviewImage (preview, data.Image.Bitmap); + SetPreviewImage (preview, data.Image.Bitmap, data.SourceFormat); } } catch (Exception X) @@ -250,7 +250,7 @@ namespace GARbro.GUI } } - void SetPreviewImage (PreviewFile preview, BitmapSource bitmap) + void SetPreviewImage (PreviewFile preview, BitmapSource bitmap, ImageFormat format) { if (bitmap.DpiX != Desktop.DpiX || bitmap.DpiY != Desktop.DpiY) { @@ -271,7 +271,7 @@ namespace GARbro.GUI ImageCanvas.Source = bitmap; ApplyDownScaleSetting(); SetStatusText (string.Format (guiStrings.MsgImageSize, bitmap.PixelWidth, - bitmap.PixelHeight, bitmap.Format.BitsPerPixel)); + bitmap.PixelHeight, bitmap.Format.BitsPerPixel, format.Tag)); } }); } diff --git a/GUI/Strings/guiStrings.ja-JP.resx b/GUI/Strings/guiStrings.ja-JP.resx index 83838d49..4ff1dda7 100644 --- a/GUI/Strings/guiStrings.ja-JP.resx +++ b/GUI/Strings/guiStrings.ja-JP.resx @@ -270,7 +270,7 @@ Extracting files from {0} to {1} - イメージ {0} x {1} x {2}bpp + イメージ {0} x {1} x {2}bpp [{3}] Image {0} x {1} x {2}bpp diff --git a/GUI/Strings/guiStrings.ko-KR.resx b/GUI/Strings/guiStrings.ko-KR.resx index 05b8ae9b..df00a764 100644 --- a/GUI/Strings/guiStrings.ko-KR.resx +++ b/GUI/Strings/guiStrings.ko-KR.resx @@ -232,7 +232,7 @@ {0}에서 {1}로 파일 추출하기 - 이미지 {0} x {1} x {2}bpp + 이미지 {0} x {1} x {2}bpp [{3}] 추출할 파일이 없음 diff --git a/GUI/Strings/guiStrings.resx b/GUI/Strings/guiStrings.resx index 2ae74247..484755bf 100644 --- a/GUI/Strings/guiStrings.resx +++ b/GUI/Strings/guiStrings.resx @@ -232,7 +232,7 @@ Extracting files from {0} to {1} - Image {0} x {1} x {2}bpp + Image {0} x {1} x {2}bpp [{3}] no files to extract diff --git a/GUI/Strings/guiStrings.ru-RU.resx b/GUI/Strings/guiStrings.ru-RU.resx index f029b16c..c19cdebd 100644 --- a/GUI/Strings/guiStrings.ru-RU.resx +++ b/GUI/Strings/guiStrings.ru-RU.resx @@ -226,7 +226,7 @@ Извлекаются файлы из {0} в {1} - Изображение {0} x {1} x {2}bpp + Изображение {0} x {1} x {2}bpp [{3}] отсутствуют файлы, удовлетворяющие выбранным критериям diff --git a/GUI/Strings/guiStrings.zh-Hans.resx b/GUI/Strings/guiStrings.zh-Hans.resx index 9a29a29d..8b5efa30 100644 --- a/GUI/Strings/guiStrings.zh-Hans.resx +++ b/GUI/Strings/guiStrings.zh-Hans.resx @@ -232,7 +232,7 @@ 正在从{0}中提取文件至{1}…… - 图像 {0} x {1} x {2}bpp + 图像 {0} x {1} x {2}bpp [{3}] 没有可以提取的文件。 From a9633988b440f7a52d747d8ccf5450f7ef03e79c Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:16:54 +0400 Subject: [PATCH 09/38] (GUI): copy images when no conversion required. --- GUI/GarConvert.cs | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/GUI/GarConvert.cs b/GUI/GarConvert.cs index 1531710a..3a8be873 100644 --- a/GUI/GarConvert.cs +++ b/GUI/GarConvert.cs @@ -228,24 +228,39 @@ namespace GARbro.GUI var src_format = ImageFormat.FindFormat (file); if (null == src_format) return; - if (src_format.Item1 == m_image_format && m_image_format.Extensions.Any (ext => ext == source_ext)) - return; - file.Position = 0; - var image = src_format.Item1.Read (file, src_format.Item2); - var output = CreateNewFile (target_name); + Stream output = null; try { - m_image_format.Write (output, image); + if (src_format.Item1 == m_image_format && m_image_format.Extensions.Any (ext => ext == source_ext)) + { + if (AreSamePaths (filename, target_name)) + return; + output = CreateNewFile (target_name); + file.Position = 0; + file.AsStream.CopyTo (output); + } + else + { + file.Position = 0; + var image = src_format.Item1.Read (file, src_format.Item2); + output = CreateNewFile (target_name); + m_image_format.Write (output, image); + } } catch // delete destination file on conversion failure { // FIXME if user chooses to overwrite file, and conversion results in error, // then original file will be lost. output.Dispose(); + output = null; File.Delete (target_name); throw; } - output.Dispose(); + finally + { + if (output != null) + output.Dispose(); + } } } @@ -264,5 +279,12 @@ namespace GARbro.GUI m_main.ListViewFocus(); m_main.RefreshView(); } + + static internal bool AreSamePaths (string filename1, string filename2) + { + filename1 = Path.GetFullPath (filename1); + filename2 = Path.GetFullPath (filename2); + return string.Equals (filename1, filename2, StringComparison.OrdinalIgnoreCase); + } } } From 9fb61e1e76df0b209cfe88ccae37d703d8d717ed Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:20:58 +0400 Subject: [PATCH 10/38] (ConsoleBrowser): fixed formats lookup. --- Console/ConsoleBrowser.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Console/ConsoleBrowser.cs b/Console/ConsoleBrowser.cs index 552b7503..c4b11043 100644 --- a/Console/ConsoleBrowser.cs +++ b/Console/ConsoleBrowser.cs @@ -90,7 +90,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); @@ -126,12 +126,12 @@ namespace GARbro { VFS.ChDir (m_arc_name); } - catch (Exception X) + catch (Exception) { Console.Error.WriteLine ("{0}: unknown format", m_arc_name); continue; } - var arc = (ArchiveFileSystem)VFS.Top; + var arc = ((ArchiveFileSystem)VFS.Top).Source; if (args.Length > argn+1) { for (int i = argn+1; i < args.Length; ++i) @@ -168,8 +168,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."); } From 051537348a6afc86cd4b2a37b8cebf2c6a65245f Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:21:27 +0400 Subject: [PATCH 11/38] (Unity): compression method 2 is LZ4 as well. --- ArcFormats/Unity/ArcUnityFS.cs | 1 + ArcFormats/Unity/BundleStream.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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/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."); From 591b091921c576bdae165ce614f5e8d863b01f3c Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:22:03 +0400 Subject: [PATCH 12/38] (QLIE): ARGB images. --- ArcFormats/Qlie/ArcQLIE.cs | 2 +- ArcFormats/Qlie/ImageARGB.cs | 114 +++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 ArcFormats/Qlie/ImageARGB.cs 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"); + } + } +} From 0e0e0fb4da9a133157dbbd8a99a18aab3eefc915 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:22:26 +0400 Subject: [PATCH 13/38] (Maika): another archive signature. --- ArcFormats/Maika/ArcMK2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 }; } From 6090c724011c8951ed8acaa82c3b82a4cb655a63 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:24:11 +0400 Subject: [PATCH 14/38] (XFL): treat embedded XFL archives as sub-directories. --- ArcFormats/Liar/ArcXFL.cs | 58 +++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 17 deletions(-) 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, From 14e2a74c224e8c1dd6f149f0ecd98948978c382d Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:25:31 +0400 Subject: [PATCH 15/38] (Kaguya): recognize compressed BM bitmaps. --- ArcFormats/Kaguya/ArcLIN2.cs | 2 +- ArcFormats/Kaguya/ArcLINK.cs | 126 ++++++++++++++++++++++++++++------- 2 files changed, 103 insertions(+), 25 deletions(-) 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 0514d70e..89f2f225 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()) From f6d9329c700c0e093cbddb0ea9c8755c18a1ef98 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:25:49 +0400 Subject: [PATCH 16/38] (Kaguya): AN10 images. --- ArcFormats/Kaguya/ArcANM.cs | 82 +++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/ArcFormats/Kaguya/ArcANM.cs b/ArcFormats/Kaguya/ArcANM.cs index a4d9145c..512513b1 100644 --- a/ArcFormats/Kaguya/ArcANM.cs +++ b/ArcFormats/Kaguya/ArcANM.cs @@ -122,6 +122,88 @@ namespace GameRes.Formats.Kaguya } } + [Export(typeof(ArchiveFormat))] + public class An10Opener : ArchiveFormat + { + public override string Tag { get { return "AN10/KAGUYA"; } } + public override string Description { get { return "KaGuYa script engine animation resource"; } } + public override uint Signature { get { return 0x30314E41; } } // 'AN10' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public An10Opener () + { + 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 + { + 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); + uint channels = file.View.ReadUInt32 (current_offset+16); + var entry = new Entry + { + Name = string.Format ("{0}#{1:D2}", base_name, i), + Type = "image", + Offset = current_offset, + Size = 0x14 + channels*width*height, + }; + dir.Add (entry); + current_offset += entry.Size; + } + 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 An10Decoder (input, base_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 * (int)Info.Width; + var pixels = m_input.ReadBytes (stride*(int)Info.Height); + PixelFormat format = 24 == Info.BPP ? PixelFormats.Bgr24 : PixelFormats.Bgra32; + return ImageData.CreateFlipped (Info, format, null, pixels, stride); + } + } + [Export(typeof(ArchiveFormat))] public class An20Opener : ArchiveFormat { From 03c03016d06e29c1dec4cf3b71a468560fdfa4c7 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:26:37 +0400 Subject: [PATCH 17/38] (TINK): tweaked 'c' type image reading. --- ArcFormats/Cyberworks/ImageTINK.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ArcFormats/Cyberworks/ImageTINK.cs b/ArcFormats/Cyberworks/ImageTINK.cs index b806d8b1..d64c6cdb 100644 --- a/ArcFormats/Cyberworks/ImageTINK.cs +++ b/ArcFormats/Cyberworks/ImageTINK.cs @@ -145,6 +145,8 @@ namespace GameRes.Formats.Cyberworks { var size_buf = new byte[4]; input.Read (size_buf, 0 , 4); + if ('c' == type) + input.ReadByte(); var decoder = new PngBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); BitmapSource frame = decoder.Frames[0]; From 7bd0e4532bd03266becdad63c00818addcf60954 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:27:44 +0400 Subject: [PATCH 18/38] (ArcDAT): more special cases work-arounds. --- ArcFormats/Cyberworks/ArcDAT.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) 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; From 54928d163151f76586e02027e83ef6b278062b67 Mon Sep 17 00:00:00 2001 From: poddav Date: Fri, 29 Apr 2022 13:29:22 +0400 Subject: [PATCH 19/38] upgraded nuget packages. --- ArcFormats/ArcFormats.csproj | 25 ++++++++--- ArcFormats/AudioOGG.cs | 6 +-- ArcFormats/Properties/AssemblyInfo.cs | 4 +- ArcFormats/app.config | 8 ++++ ArcFormats/packages.config | 12 +++-- Experimental/Experimental.csproj | 60 ++++++++++++++++++++++--- Experimental/Properties/AssemblyInfo.cs | 4 +- Experimental/packages.config | 29 ++++++++++-- GUI/GARbro.GUI.csproj | 5 +-- GUI/Properties/AssemblyInfo.cs | 4 +- GUI/packages.config | 2 +- GameRes/GameRes.csproj | 5 +-- GameRes/Properties/AssemblyInfo.cs | 4 +- GameRes/packages.config | 2 +- Legacy/Legacy.csproj | 3 ++ Legacy/Properties/AssemblyInfo.cs | 4 +- Legacy/app.config | 15 +++++++ 17 files changed, 150 insertions(+), 42 deletions(-) create mode 100644 Legacy/app.config diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 95ff52b7..b7178cc6 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -49,25 +49,35 @@ MinimumRecommendedRules.ruleset - - ..\packages\SharpZipLib.1.1.0\lib\net45\ICSharpCode.SharpZipLib.dll - True + + ..\packages\SharpZipLib.1.3.3\lib\net45\ICSharpCode.SharpZipLib.dll ..\packages\NAudio.1.7.3\lib\net35\NAudio.dll - - ..\packages\NVorbis.0.8.6\lib\net35\NVorbis.dll - True + + ..\packages\NVorbis.0.10.4\lib\net45\NVorbis.dll + + ..\packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.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 + @@ -95,6 +105,8 @@ + + @@ -296,6 +308,7 @@ + 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/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/app.config b/ArcFormats/app.config index 4ab43b0e..f2b7e940 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -220,6 +220,14 @@ + + + + + + + + diff --git a/ArcFormats/packages.config b/ArcFormats/packages.config index 5a94be48..c9e8b0fb 100644 --- a/ArcFormats/packages.config +++ b/ArcFormats/packages.config @@ -1,9 +1,13 @@  - - + + + - - + + + + + \ No newline at end of file diff --git a/Experimental/Experimental.csproj b/Experimental/Experimental.csproj index a828663d..3240aae5 100644 --- a/Experimental/Experimental.csproj +++ b/Experimental/Experimental.csproj @@ -11,7 +11,8 @@ ArcExtra v4.6 512 - d57ff7cf + + ..\ true @@ -51,8 +52,9 @@ ..\packages\Concentus.1.1.7\lib\portable-net45+win+wpa81+wp80\Concentus.dll - - ..\packages\Concentus.OggFile.1.0.3\lib\portable-net45+win+wpa81+wp80\Concentus.Oggfile.dll + + ..\packages\Concentus.Oggfile.1.0.4\lib\net45\Concentus.Oggfile.dll + True ..\packages\MSFTCompressionCab.1.0.0\lib\Microsoft.Deployment.Compression.dll @@ -62,12 +64,36 @@ + + ..\packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll + + + ..\packages\System.Console.4.3.1\lib\net46\System.Console.dll + - - ..\packages\System.Data.SQLite.Core.1.0.109.2\lib\net46\System.Data.SQLite.dll + + ..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.115.5\lib\net46\System.Data.SQLite.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 + @@ -75,7 +101,13 @@ + + ..\packages\System.Xml.ReaderWriter.4.3.1\lib\net46\System.Xml.ReaderWriter.dll + + + ..\packages\ZstdNet.1.4.5\lib\net45\ZstdNet.dll + @@ -88,6 +120,7 @@ + @@ -101,6 +134,7 @@ + PreserveNewest @@ -135,6 +169,16 @@ + + + PreserveNewest + + + + + PreserveNewest + + ipt @@ -156,10 +200,10 @@ exit 0 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}. - + + - @@ -172,6 +216,8 @@ exit 0 + +