diff --git a/ArcFormats/DigitalWorks/ImageTM2.cs b/ArcFormats/DigitalWorks/ImageTM2.cs index b5ce73f4..3c20f973 100644 --- a/ArcFormats/DigitalWorks/ImageTM2.cs +++ b/ArcFormats/DigitalWorks/ImageTM2.cs @@ -34,20 +34,20 @@ namespace GameRes.Formats.DigitalWorks { internal class Tim2MetaData : ImageMetaData { - public int PaletteSize; - public int HeaderSize; - public int Colors; + public int PaletteSize; + public int HeaderSize; + public int Colors; public byte Alpha; } [Export(typeof(ImageFormat))] public class Tim2Format : ImageFormat { - public override string Tag { get { return "TIM2"; } } + public override string Tag { get { return "TIM2"; } } public override string Description { get { return "PlayStation/2 image format"; } } - public override uint Signature { get { return 0x324D4954; } } // 'TIM2' + public override uint Signature { get { return 0x324D4954; } } // 'TIM2' - public Tim2Format () + public Tim2Format() { Extensions = new string[] { "tm2", "ext" }; Settings = new[] { AlphaFormat }; @@ -60,18 +60,18 @@ namespace GameRes.Formats.DigitalWorks ValuesSet = new[] { "No Alpha", "RGBX", "RGBA" }, }; - public override ImageMetaData ReadMetaData (IBinaryStream file) + public override ImageMetaData ReadMetaData(IBinaryStream file) { - var header = file.ReadHeader (0x40); + var header = file.ReadHeader(0x40); byte bpp = header[0x23]; switch (bpp) { - case 1: bpp = 16; break; - case 2: bpp = 24; break; - case 3: bpp = 32; break; - case 4: bpp = 4; break; //16color - case 5: bpp = 8; break; - default: return null; + case 1: bpp = 16; break; + case 2: bpp = 24; break; + case 3: bpp = 32; break; + case 4: bpp = 4; break; // 16 color + case 5: bpp = 8; break; + default: return null; } byte alpha; switch (AlphaFormat.Get()) @@ -81,71 +81,86 @@ namespace GameRes.Formats.DigitalWorks case "RGBA": default: alpha = 8; break; } - return new Tim2MetaData { - Width = header.ToUInt16 (0x24), - Height = header.ToUInt16 (0x26), - BPP = bpp, - PaletteSize = header.ToInt32 (0x14), - HeaderSize = header.ToUInt16 (0x1C), - Colors = header.ToUInt16 (0x1E), - Alpha = alpha, //header.ToUInt16(0x30) == 0?// not so sure, there will be omissions + return new Tim2MetaData + { + Width = header.ToUInt16(0x24), + Height = header.ToUInt16(0x26), + BPP = bpp, + PaletteSize = header.ToInt32(0x14), + HeaderSize = header.ToUInt16(0x1C), + Colors = header.ToUInt16(0x1E), + Alpha = alpha, }; } - public override ImageData Read (IBinaryStream file, ImageMetaData info) + public override ImageData Read(IBinaryStream file, ImageMetaData info) { - var reader = new Tim2Reader (file, (Tim2MetaData)info); + var reader = new Tim2Reader(file, (Tim2MetaData)info); var pixels = reader.Unpack(); - return ImageData.Create (info, reader.Format, reader.Palette, pixels); + return ImageData.Create(info, reader.Format, reader.Palette, pixels); } - public override void Write (Stream file, ImageData image) + public override void Write(Stream file, ImageData image) { - throw new System.NotImplementedException ("Tim2Format.Write not implemented"); + throw new System.NotImplementedException("Tim2Format.Write not implemented"); } } internal class Tim2Reader { - IBinaryStream m_input; - Tim2MetaData m_info; + IBinaryStream m_input; + Tim2MetaData m_info; - public PixelFormat Format { get; private set; } + public PixelFormat Format { get; private set; } public BitmapPalette Palette { get; private set; } - public Tim2Reader (IBinaryStream input, Tim2MetaData info) + public Tim2Reader(IBinaryStream input, Tim2MetaData info) { m_input = input; m_info = info; switch (info.BPP) { - case 4: Format = PixelFormats.Indexed4; break; - case 8: Format = PixelFormats.Indexed8; break; + case 4: Format = PixelFormats.Indexed4; break; + case 8: Format = PixelFormats.Indexed8; break; case 16: Format = PixelFormats.Bgr555; break; - case 24: Format = PixelFormats.Bgr24; break; + case 24: Format = PixelFormats.Bgr24; break; case 32: Format = PixelFormats.Bgra32; break; } } - public byte[] Unpack () + public byte[] Unpack() { m_input.Position = 0x10 + m_info.HeaderSize; double pixel_size = (double)m_info.BPP / 8; int image_size = (int)((int)m_info.Width * (int)m_info.Height * pixel_size); - var output = m_input.ReadBytes (image_size); - if (pixel_size <= 8 && m_info.Colors > 0) - Palette = ReadPalette (m_info.Colors, m_info.Alpha); + var output = m_input.ReadBytes(image_size); - if (pixel_size == 3 || pixel_size == 4 && m_info.Alpha == 8) + if (pixel_size <= 1 && m_info.Colors > 0) // Indexed images + Palette = ReadPalette(m_info.Colors, m_info.Alpha); + + // Handle 24bpp and 32bpp (Direct Color) + if (pixel_size == 3 || (pixel_size == 4 && m_info.Alpha == 8)) { for (int i = 0; i < image_size; i += (int)pixel_size) { + // Swap R and B channels byte r = output[i]; - output[i] = output[i+2]; - output[i+2] = r; + output[i] = output[i + 2]; + output[i + 2] = r; + + // Fix Alpha for 32bpp (Double Alpha Scaling) + if (pixel_size == 4) + { + byte a = output[i + 3]; + if (a > 0) + { + output[i + 3] = (byte)Math.Min(255, a * 2); + } + } } } - if (pixel_size == 4 && m_info.Alpha == 7) + // Handle specific RGBX (Alpha 7) case + else if (pixel_size == 4 && m_info.Alpha == 7) { for (int i = 0; i < image_size; i += 4) { @@ -158,7 +173,8 @@ namespace GameRes.Formats.DigitalWorks output[i + 3] = (byte)(output[i + 3] << 1); } } - if (pixel_size == 4 && m_info.Alpha == 0) + // Handle No Alpha case + else if (pixel_size == 4 && m_info.Alpha == 0) { for (int i = 0; i < image_size; i += 4) { @@ -171,13 +187,29 @@ namespace GameRes.Formats.DigitalWorks return output; } - BitmapPalette ReadPalette (int color_num, byte X_A = 8) + BitmapPalette ReadPalette(int color_num, byte X_A = 8) { - var source = ImageFormat.ReadColorMap (m_input.AsStream, + var source = ImageFormat.ReadColorMap(m_input.AsStream, color_num, X_A == 7 ? PaletteFormat.RgbA7 : X_A == 0 ? PaletteFormat.RgbX : PaletteFormat.RgbA); + + // Scaled Alpha for Indexed Palettes (KID/PS2 Fix) + if (X_A != 0) + { + for (int i = 0; i < source.Length; i++) + { + byte a = source[i].A; + if (a > 0) + { + byte newAlpha = (byte)Math.Min(255, a * 2); + source[i] = Color.FromArgb(newAlpha, source[i].R, source[i].G, source[i].B); + } + } + } + var color_map = new Color[color_num]; - if (color_num == 16){ + if (color_num == 16) + { Array.Copy(source, 0, color_map, 0, 16); return new BitmapPalette(color_map); } @@ -189,14 +221,14 @@ namespace GameRes.Formats.DigitalWorks int dst = 0; for (int part = 0; part < parts; part++) - for (int block = 0; block < blocks; block++) - for (int row = 0; row < rows; row++) - { - int src = (part * rows * blocks + row * rows + block) * colors; - Array.Copy (source, src, color_map, dst, colors); - dst += colors; - } - return new BitmapPalette (color_map); + for (int block = 0; block < blocks; block++) + for (int row = 0; row < rows; row++) + { + int src = (part * rows * blocks + row * rows + block) * colors; + Array.Copy(source, src, color_map, dst, colors); + dst += colors; + } + return new BitmapPalette(color_map); } } -} +} \ No newline at end of file diff --git a/ArcFormats/Kid/ArcP2T.cs b/ArcFormats/Kid/ArcP2T.cs new file mode 100644 index 00000000..56aff398 --- /dev/null +++ b/ArcFormats/Kid/ArcP2T.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using GameRes.Compression; + +namespace GameRes.Formats.KID +{ + // Custom entry class to store metadata required for P2T reconstruction + public class P2tEntry : Entry + { + public byte[] TimHeaderChunk { get; set; } + public int RealUncompressedSize { get; set; } + public uint CompressedSize { get; set; } + } + + [Export(typeof(ArchiveFormat))] + public class P2tOpener : ArchiveFormat + { + public override string Tag { get { return "P2T"; } } + public override string Description { get { return "KID P2T archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + // Standard TIM2 header magic prefix + private static readonly byte[] Tim2Magic = new byte[] { + 0x54, 0x49, 0x4D, 0x32, 0x04, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + public override ArcFile TryOpen(ArcView file) + { + if (file.MaxOffset < 0x20) return null; + + uint headerEnd = file.View.ReadUInt32(0x08); + uint numFiles = file.View.ReadUInt32(0x0C); + uint dataStartBase = file.View.ReadUInt32(0x10); + + if (numFiles == 0 || numFiles > 0xFFFF || headerEnd >= file.MaxOffset) return null; + + // Basic validation check at the start of the file table + uint checkFF = file.View.ReadUInt32(headerEnd + 48); + if (checkFF != 0xFFFFFFFF) return null; + + var dir = new List((int)numFiles); + long currentEntryPtr = headerEnd; + const int EntrySize = 64; + + for (int i = 0; i < numFiles; i++) + { + // Mapping structure: + // 48-51: FFFFFFFF padding + // 52-55: Data Offset (Raw) + // 56-59: Flag (usually 01) + // 60-63: Compressed Length + uint rawOffset = file.View.ReadUInt32(currentEntryPtr + 52); + uint compressedLen = file.View.ReadUInt32(currentEntryPtr + 60); + + long sizeInfoOffset = dataStartBase + rawOffset; + if (sizeInfoOffset + 4 > file.MaxOffset) break; + + uint realUncompressedSize = file.View.ReadUInt32(sizeInfoOffset); + byte[] timHeaderChunk = file.View.ReadBytes(currentEntryPtr, 48); + + var entry = new P2tEntry + { + Name = string.Format("{0:D4}.tm2", i), + Type = "image", + Offset = sizeInfoOffset + 4, + Size = realUncompressedSize, // Display uncompressed size in list + RealUncompressedSize = (int)realUncompressedSize, + CompressedSize = compressedLen, + TimHeaderChunk = timHeaderChunk + }; + + dir.Add(entry); + currentEntryPtr += EntrySize; + } + + return new ArcFile(file, this, dir); + } + + public override Stream OpenEntry(ArcFile arc, Entry entry) + { + var p2tEntry = entry as P2tEntry; + if (p2tEntry == null) return base.OpenEntry(arc, entry); + + // Read the compressed block using the stored length + var compressedData = arc.File.View.ReadBytes(entry.Offset, p2tEntry.CompressedSize); + + // LZSS decompression (Okumura variant) + var decompressedBody = LzssDecompress(compressedData); + + // Align data to expected uncompressed size + if (decompressedBody.Length != p2tEntry.RealUncompressedSize) + Array.Resize(ref decompressedBody, p2tEntry.RealUncompressedSize); + + // Reconstruct final file: Magic + Table Header Chunk + Decompressed Data + int finalSize = Tim2Magic.Length + p2tEntry.TimHeaderChunk.Length + decompressedBody.Length; + var outputData = new byte[finalSize]; + + Buffer.BlockCopy(Tim2Magic, 0, outputData, 0, Tim2Magic.Length); + Buffer.BlockCopy(p2tEntry.TimHeaderChunk, 0, outputData, Tim2Magic.Length, p2tEntry.TimHeaderChunk.Length); + Buffer.BlockCopy(decompressedBody, 0, outputData, Tim2Magic.Length + p2tEntry.TimHeaderChunk.Length, decompressedBody.Length); + + return new MemoryStream(outputData); + } + + private byte[] LzssDecompress(byte[] input) + { + var output = new List(input.Length * 2); + const int n = 4096; + const int threshold = 2; + int r = n - 18; + var textBuf = new byte[n + 18 - 1]; + Array.Clear(textBuf, 0, textBuf.Length); + + int flags = 0; + int srcPos = 0; + + while (srcPos < input.Length) + { + flags >>= 1; + if ((flags & 0x100) == 0) + { + if (srcPos >= input.Length) break; + flags = input[srcPos++] | 0xFF00; + } + + if ((flags & 1) != 0) + { + if (srcPos >= input.Length) break; + byte c = input[srcPos++]; + output.Add(c); + textBuf[r] = c; + r = (r + 1) & (n - 1); + } + else + { + if (srcPos + 1 >= input.Length) break; + int i = input[srcPos++]; + int j = input[srcPos++]; + int offset = i | ((j & 0xF0) << 4); + int len = (j & 0x0F) + threshold; + for (int k = 0; k <= len; k++) + { + byte c = textBuf[(offset + k) & (n - 1)]; + output.Add(c); + textBuf[r] = c; + r = (r + 1) & (n - 1); + } + } + } + return output.ToArray(); + } + } +} \ No newline at end of file