From 60b0f0bf40d05a646c92f2b721b5aa5888a08740 Mon Sep 17 00:00:00 2001 From: Crsky Date: Tue, 17 Mar 2026 10:44:30 +0800 Subject: [PATCH] feat: Add TLG mux qoi archive --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/KiriKiri/ArcTLG.cs | 109 ++++++++++++++++++++++++++++++++ ArcFormats/KiriKiri/ImageTLG.cs | 3 +- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 ArcFormats/KiriKiri/ArcTLG.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 0970026f..b410cb58 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -204,6 +204,7 @@ + diff --git a/ArcFormats/KiriKiri/ArcTLG.cs b/ArcFormats/KiriKiri/ArcTLG.cs new file mode 100644 index 00000000..a4d7f568 --- /dev/null +++ b/ArcFormats/KiriKiri/ArcTLG.cs @@ -0,0 +1,109 @@ +//! \file ArcTLG.cs +//! \date Tue Mar 17 2026 10:35:55 +//! \brief KiriKiri TLG image implementation. +//--------------------------------------------------------------------------- +// TLGqoi multi-layer image decoder +// +// C# port by crsky +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.KiriKiri +{ + internal class TlgLayerEntry : Entry + { + public int Index; + } + + [Export(typeof(ArchiveFormat))] + public class TlgOpener : ArchiveFormat + { + public override string Tag { get { return "TLG"; } } + public override string Description { get { return "KiriKiri game engine image format"; } } + public override uint Signature { get { return 0x71474C54; } } // 'TLGq' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public TlgOpener () + { + Extensions = new string[] { "tlg" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "TLGqoi") || !file.View.AsciiEqual (7, "raw")) + return null; + var qhdr = Array.Empty (); + var offset = 0x14; + while (true) + { + var entry_signature = file.View.ReadInt32 (offset); + var entry_size = file.View.ReadInt32 (offset+4); + offset += 8; + if (0x52444851 == entry_signature) // 'QHDR' + { + if (0x30 != entry_size) + return null; + qhdr = file.View.ReadBytes (offset, (uint)entry_size); + if (entry_size != qhdr.Length) + return null; + offset += entry_size; + } + else if (0 == entry_signature && 0 == entry_size) + break; + else + return null; + } + if (0 == qhdr.Length) + return null; + var layer_count = qhdr.ToInt32 (4); + if (layer_count < 1) + return null; + var block_count = qhdr.ToInt32 (12); + if (0 == block_count) + return null; + var dir = new List (layer_count); + for (var i = 0; i < layer_count; i++) + { + dir.Add (new TlgLayerEntry + { + Name = string.Format ("{0}#{1:D3}.tlg", Path.GetFileNameWithoutExtension (file.Name), i), + Size = (uint)file.MaxOffset, + Type = "image", + Index = i, + }); + } + return new ArcFile (file, this, dir); + } + + static readonly ResourceInstance s_TlgFormat = new ResourceInstance ("TLG"); + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var layer_entry = entry as TlgLayerEntry; + if (null == layer_entry) + return base.OpenImage (arc, entry); + var input = arc.File.CreateStream (); + try + { + var info = s_TlgFormat.Value.ReadMetaData (input); + if (null == info) + throw new InvalidFormatException (); + if (info is TlgMetaData tlg) + { + tlg.LayerIndex = layer_entry.Index; + } + return new ImageFormatDecoder (input, s_TlgFormat.Value, info); + } + catch + { + input.Dispose (); + throw; + } + } + } +} diff --git a/ArcFormats/KiriKiri/ImageTLG.cs b/ArcFormats/KiriKiri/ImageTLG.cs index a715900f..3d533b70 100644 --- a/ArcFormats/KiriKiri/ImageTLG.cs +++ b/ArcFormats/KiriKiri/ImageTLG.cs @@ -25,6 +25,7 @@ namespace GameRes.Formats.KiriKiri { public int Version; public long DataOffset; + public int LayerIndex; } [Export(typeof(ImageFormat))] @@ -1520,7 +1521,7 @@ namespace GameRes.Formats.KiriKiri throw new InvalidFormatException (); } if (0 != qhdr.Length) - return DecodeMultiLayerQOI (src, info.Width, info.Height, qhdr, 0); + return DecodeMultiLayerQOI (src, info.Width, info.Height, qhdr, info.LayerIndex); return DecodeQOI (src, info.Width, info.Height); }