Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 171 additions & 3 deletions ArcFormats/KiriKiri/ImageTLG.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace GameRes.Formats.KiriKiri
internal class TlgMetaData : ImageMetaData
{
public int Version;
public int DataOffset;
public long DataOffset;
}

[Export(typeof(ImageFormat))]
Expand All @@ -35,7 +35,7 @@ public class TlgFormat : ImageFormat
public TlgFormat ()
{
Extensions = new string[] { "tlg", "tlg5", "tlg6" };
Signatures = new uint[] { 0x30474C54, 0x35474C54, 0x36474C54, 0x35474CAB, 0x584D4B4A };
Signatures = new uint[] { 0x30474C54, 0x35474C54, 0x36474C54, 0x35474CAB, 0x584D4B4A, 0x6D474C54, 0x71474C54 };
}

public override ImageMetaData ReadMetaData (IBinaryStream stream)
Expand All @@ -45,14 +45,18 @@ public override ImageMetaData ReadMetaData (IBinaryStream stream)
if (!header.AsciiEqual ("TLG0.0\x00sds\x1a"))
offset = 0;
int version;
if (!header.AsciiEqual (offset+6, "\x00raw\x1a"))
if (!header.AsciiEqual (offset+6, "\x00raw\x1a") && !header.AsciiEqual (offset+6, "\x00idx\x1a"))
return null;
if (0xAB == header[offset])
header[offset] = (byte)'T';
if (header.AsciiEqual (offset, "TLG6.0"))
version = 6;
else if (header.AsciiEqual (offset, "TLG5.0"))
version = 5;
else if (header.AsciiEqual (offset, "TLGmux"))
version = 0;
else if (header.AsciiEqual (offset, "TLGqoi"))
version = 1;
else if (header.AsciiEqual (offset, "XXXYYY"))
{
version = 5;
Expand Down Expand Up @@ -137,6 +141,10 @@ byte[] ReadTlg (IBinaryStream src, TlgMetaData info)
src.Position = info.DataOffset;
if (6 == info.Version)
return ReadV6 (src, info);
else if (0 == info.Version)
return ReadMUX (src, info);
else if (1 == info.Version)
return ReadQOI (src, info);
else
return ReadV5 (src, info);
}
Expand Down Expand Up @@ -1085,6 +1093,166 @@ decode values packed in "bit_pool".
}
}
}

static class QoiCodec
{
public const int Index = 0x00;
public const int Diff = 0x40;
public const int Luma = 0x80;
public const int Run = 0xC0;
public const int Rgb = 0xFE;
public const int Rgba = 0xFF;
public const int Mask2 = 0xC0;
public const int HashTableSize = 64;
}

byte[] DecodeQOI (IBinaryStream src, uint width, uint height)
{
var output = new byte[4*width*height];
var table = new byte[4*QoiCodec.HashTableSize];
byte r = 0, g = 0, b = 0, a = 255;
var run = 0;
for (var dst = 0; dst < output.Length; dst += 4)
{
if (run > 0)
run--;
else
{
var b1 = src.ReadByte ();
if (-1 == b1)
throw new EndOfStreamException ();
if (QoiCodec.Rgb == b1)
{
var rgb = src.ReadInt24 ();
r = (byte)rgb;
g = (byte)(rgb >> 8);
b = (byte)(rgb >> 16);
}
else if (QoiCodec.Rgba == b1)
{
var rgba = src.ReadInt32 ();
r = (byte)rgba;
g = (byte)(rgba >> 8);
b = (byte)(rgba >> 16);
a = (byte)(rgba >> 24);
}
else if (QoiCodec.Index == (b1 & QoiCodec.Mask2))
{
var p1 = (b1 & ~QoiCodec.Mask2) * 4;
r = table[p1 ];
g = table[p1+1];
b = table[p1+2];
a = table[p1+3];
}
else if (QoiCodec.Diff == (b1 & QoiCodec.Mask2))
{
r += (byte)(((b1 >> 4) & 0x03) - 2);
g += (byte)(((b1 >> 2) & 0x03) - 2);
b += (byte)((b1 & 0x03) - 2);
}
else if (QoiCodec.Luma == (b1 & QoiCodec.Mask2))
{
var b2 = src.ReadByte ();
if (-1 == b2)
throw new EndOfStreamException ();
var vg = (b1 & 0x3F) - 32;
r += (byte)(vg - 8 + ((b2 >> 4) & 0x0F));
g += (byte)vg;
b += (byte)(vg - 8 + (b2 & 0x0F));
}
else if (QoiCodec.Run == (b1 & QoiCodec.Mask2))
{
run = b1 & 0x3F;
}
var p2 = (r*3 + g*5 + b*7 + a*11) % QoiCodec.HashTableSize*4;
table[p2 ] = r;
table[p2+1] = g;
table[p2+2] = b;
table[p2+3] = a;
}
output[dst ] = b;
output[dst+1] = g;
output[dst+2] = r;
output[dst+3] = a;
}
return output;
}

byte[] ReadQOI (IBinaryStream src, TlgMetaData info)
{
while (true)
{
var entry_signature = src.ReadInt32 ();
var entry_size = src.ReadInt32 ();
if (0x52444851 == entry_signature) // 'QHDR'
{
throw new NotImplementedException ();
}
else if (0 == entry_signature && 0 == entry_size)
break;
else
throw new InvalidFormatException ();
}
return DecodeQOI (src, info.Width, info.Height);
}

byte[] ReadMUX (IBinaryStream src, TlgMetaData info)
{
src.Position = info.DataOffset;
var slices = new List<TlgMetaData> ();
while (true)
{
var entry_signature = src.ReadInt32 ();
var entry_size = src.ReadInt32 ();
if (0x58554D43 == entry_signature) // 'CMUX'
{
var entry = src.ReadBytes (entry_size);
var count = entry.ToInt32 (0);
if (0 == count)
throw new InvalidFormatException ();
var offset = 4;
for (var i = 0; i < count; i++)
{
slices.Add (new TlgMetaData
{
OffsetX = entry.ToInt32 (offset),
OffsetY = entry.ToInt32 (offset+4),
Width = entry.ToUInt32 (offset+8),
Height = entry.ToUInt32 (offset+12),
DataOffset = entry.ToInt64 (offset+16)
});
offset += 24;
}
}
else if (0 == entry_signature && 0 == entry_size)
break;
else
throw new InvalidFormatException ();
}
var data_offset = src.Position;
var image = new byte[4*info.Width*info.Height];
foreach (var slice_info in slices)
{
src.Position = data_offset + slice_info.DataOffset;
byte[] slice;
var header = src.ReadBytes (11);
if (header.AsciiEqual (0, "TLGqoi") && header.AsciiEqual (7, "raw"))
{
var channels = src.ReadByte ();
var width = src.ReadUInt32 ();
var height = src.ReadUInt32 ();
if (3 != channels && 4 != channels)
throw new InvalidFormatException ();
if (width != slice_info.Width || height != slice_info.Height)
throw new InvalidFormatException ();
slice = ReadQOI (src, slice_info);
}
else
throw new NotImplementedException ();
BlendImage (image, info, slice, slice_info, 0);
}
return image;
}
}

internal class TagsParser
Expand Down
Loading