diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index a45bf627..d98775fc 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -122,6 +122,7 @@ + diff --git a/ArcFormats/Lambda/ArcCLS.cs b/ArcFormats/Lambda/ArcCLS.cs index f0130699..633b6149 100644 --- a/ArcFormats/Lambda/ArcCLS.cs +++ b/ArcFormats/Lambda/ArcCLS.cs @@ -50,16 +50,37 @@ namespace GameRes.Formats.Lambda var dir = new List (count); for (int i = 0; i < count; ++i) { - var name = file.View.ReadString (index_offset, 0x28); - uint offset = file.View.ReadUInt32 (index_offset+0x2C); - var entry = AutoEntry.Create (file, offset, name); - entry.Size = file.View.ReadUInt32 (index_offset+0x30); + var entry = new Entry { + Name = file.View.ReadString (index_offset, 0x28), + Offset = file.View.ReadUInt32 (index_offset+0x2C), + Size = file.View.ReadUInt32 (index_offset+0x30), + }; if (!entry.CheckPlacement (file.MaxOffset)) return null; dir.Add (entry); index_offset += 0x40; } + DetectFileTypes (file, dir); return new ArcFile (file, this, dir); } + + void DetectFileTypes (ArcView file, IEnumerable dir) + { + foreach (var entry in dir) + { + uint signature = file.View.ReadUInt32 (entry.Offset); + if (0x5F534C43 == signature) // 'CLS_' + { + if (file.View.AsciiEqual (entry.Offset+4, "TEXFILE")) + entry.Type = "image"; + } + else + { + var res = AutoEntry.DetectFileType (signature); + if (res != null) + entry.ChangeType (res); + } + } + } } } diff --git a/ArcFormats/Lambda/ImageCLS.cs b/ArcFormats/Lambda/ImageCLS.cs new file mode 100644 index 00000000..a9876415 --- /dev/null +++ b/ArcFormats/Lambda/ImageCLS.cs @@ -0,0 +1,247 @@ +//! \file ImageCLS.cs +//! \date 2017 Dec 21 +//! \brief Lambda engine image format. +// +// Copyright (C) 2017-2018 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; +using GameRes.Utility; + +namespace GameRes.Formats.Lambda +{ + internal class ClsMetaData : ImageMetaData + { + public int FrameOffset; + } + + [Export(typeof(ImageFormat))] + public class ClsFormat : ImageFormat + { + public override string Tag { get { return "CLS"; } } + public override string Description { get { return "Lambda engine image format"; } } + public override uint Signature { get { return 0x5F534C43; } } // 'CLS_' + + public ClsFormat () + { + Extensions = new string[] { "" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x18); + if (!header.AsciiEqual ("CLS_TEXFILE")) + return null; + + file.Position = header.ToUInt32 (0x14); + int frame_offset = file.ReadInt32(); + file.Position = frame_offset+4; + if (file.ReadUInt16() != 1) + return null; + + file.Position = frame_offset+0x1C; + uint width = file.ReadUInt32(); + uint height = file.ReadUInt32(); + int x = file.ReadInt32(); + int y = file.ReadInt32(); + + file.Position = frame_offset+0x30; + if (file.ReadByte() != 1) + return null; + int format = file.ReadByte(); + if (format != 4 && format != 5 && format != 2) + return null; + return new ClsMetaData { + Width = width, + Height = height, + OffsetX = x, + OffsetY = y, + BPP = 8 * (format - 1), + FrameOffset = frame_offset, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new ClsReader (file, (ClsMetaData)info); + var pixels = reader.Unpack(); + return ImageData.Create (info, reader.Format, reader.Palette, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("ClsFormat.Write not implemented"); + } + } + + internal class ClsReader + { + IBinaryStream m_input; + byte[] m_channel; + int m_width; + int m_height; + int m_channels; + int m_base_offset; + int[] m_rows_sizes; + + public PixelFormat Format { get; private set; } + public BitmapPalette Palette { get; private set; } + + public ClsReader (IBinaryStream input, ClsMetaData info) + { + m_input = input; + m_base_offset = info.FrameOffset; + m_width = (int)info.Width; + m_height = (int)info.Height; + m_channels = info.BPP / 8; + if (1 == m_channels) + Format = PixelFormats.Indexed8; + else if (4 == m_channels) + Format = PixelFormats.Bgra32; + else + Format = PixelFormats.Bgr32; + m_channel = new byte[m_width * m_height]; + m_rows_sizes = new int[m_height]; + } + + static readonly byte[] ChannelOrder = { 2, 1, 0, 3 }; + + public byte[] Unpack () + { + SetPosition (0x48); + var offsets = new int[m_channels]; + for (int i = 0; i < m_channels; ++i) + offsets[i] = m_input.ReadInt32(); + SetPosition (0x58); + var sizes = new int[m_channels]; + for (int i = 0; i < m_channels; ++i) + sizes[i] = m_input.ReadInt32(); + if (1 == m_channels) + { + SetPosition (0x68); + int palette_offset = m_input.ReadInt32(); + int palette_size = m_input.ReadInt32(); + SetPosition (palette_offset); + Palette = ImageFormat.ReadPalette (m_input.AsStream, palette_size / 4, PaletteFormat.BgrA); + SetPosition (offsets[0]); + UnpackChannel (sizes[0]); + return m_channel; + } + var output = new byte[m_width * m_height * 4]; + for (int i = 0; i < m_channels; ++i) + { + SetPosition (offsets[i]); + UnpackChannel (sizes[i]); + int src = 0; + for (int dst = ChannelOrder[i]; dst < output.Length; dst += 4) + { + output[dst] = m_channel[src++]; + } + } + return output; + } + + void SetPosition (int pos) + { + m_input.Position = m_base_offset + pos; + } + + void UnpackChannel (int size) + { + int method = Binary.BigEndian (m_input.ReadUInt16()); + if (method > 1) + throw new InvalidFormatException(); + size -= 2; + if (0 == method) + ReadV0 (size); + else + ReadV1 (size); + } + + void ReadV0 (int size) + { + int row_width = size / m_height; + if (row_width == m_width) + { + m_input.Read (m_channel, 0, m_channel.Length); + } + else + { + int dst = 0; + for (int y = 0; y < m_height; ++y) + { + m_input.Read (m_channel, dst, row_width); + dst += m_width; + } + } + } + + void ReadV1 (int size) + { + int row_count = 0; + while (size > 0) + { + int chunk_size = Binary.BigEndian (m_input.ReadUInt16()); + if (row_count < m_height) + { + m_rows_sizes[row_count++] = chunk_size; + } + size -= chunk_size + 2; + } + if (size < 0) + throw new InvalidFormatException(); + int dst = 0; + for (int y = 0; y < row_count; ++y) + { + int width = m_width; + int chunk_size = m_rows_sizes[y]; + while (chunk_size --> 0) + { + byte rle = m_input.ReadUInt8(); + if (rle < 0x81) + { + int count = rle + 1; + width -= count; + chunk_size -= count; + m_input.Read (m_channel, dst, count); + dst += count; + } + else + { + int count = 0x101 - rle; + width -= count; + --chunk_size; + byte v = m_input.ReadUInt8(); + while (count --> 0) + { + m_channel[dst++] = v; + } + } + } + while (width --> 0) + m_channel[dst++] = 0; + } + } + } +}