diff --git a/ArcFormats/RealLive/ArcG00.cs b/ArcFormats/RealLive/ArcG00.cs new file mode 100644 index 00000000..5273516e --- /dev/null +++ b/ArcFormats/RealLive/ArcG00.cs @@ -0,0 +1,164 @@ +//! \file ArcG00.cs +//! \date Wed May 04 13:21:19 2016 +//! \brief RealLive engine multi-frame image. +// +// Copyright (C) 2016 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; +using System.Linq; + +namespace GameRes.Formats.RealLive +{ + internal class G00Entry : PackedEntry + { + public override string Type { get { return "image"; } } + public int X; + public int Y; + } + + internal class G00Archive : ArcFile + { + public ImageMetaData ImageInfo; + public byte[] Bitmap; + + public G00Archive (ArcView arc, ArchiveFormat impl, ICollection dir, ImageMetaData info, byte[] bitmap) + : base (arc, impl, dir) + { + ImageInfo = info; + Bitmap = bitmap; + } + } + + [Export(typeof(ArchiveFormat))] + public class G00Opener : ArchiveFormat + { + public override string Tag { get { return "G00/v2"; } } + public override string Description { get { return "RealLive engine multi-frame image"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public G00Opener () + { + Extensions = new string[] { "g00" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.EndsWith (".g00", StringComparison.InvariantCultureIgnoreCase)) + return null; + if (file.View.ReadByte (0) != 2) + return null; + uint width = file.View.ReadUInt16 (1); + uint height = file.View.ReadUInt16 (3); + if (0 == width || width > 0x8000 || 0 == height || height > 0x8000) + return null; + int count = file.View.ReadInt16 (5); + if (count <= 1 || count > 0x100) + return null; + var base_name = Path.GetFileNameWithoutExtension (file.Name); + uint size = width * height * 4 + 0x12; // virtual TGA image size + + uint index_offset = 9; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new G00Entry { + Name = string.Format ("{0}#{1:D3}.tga", base_name, i), + X = file.View.ReadInt32 (index_offset), + Y = file.View.ReadInt32 (index_offset+4), + IsPacked = true, + UnpackedSize = size, + }; + dir.Add (entry); + index_offset += 0x18; + } + byte[] bitmap; + using (var input = file.CreateStream (index_offset)) + using (var bin = new BinaryReader (input)) + bitmap = G00Reader.LzDecompress (bin, 2, 1); + + using (var input = new MemoryStream (bitmap)) + using (var reader = new BinaryReader (input)) + { + if (reader.ReadInt32() != count) + return null; + for (int i = 0; i < count; ++i) + { + dir[i].Offset = reader.ReadUInt32(); + dir[i].Size = reader.ReadUInt32(); + } + } + dir = dir.Where (e => e.Size != 0).ToList(); + if (0 == dir.Count) + return null; + var info = new ImageMetaData { Width = width, Height = height, BPP = 32 }; + return new G00Archive (file, this, dir, info, bitmap); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (0 == entry.Size) + return Stream.Null; + var g00arc = (G00Archive)arc; + var g00ent = (G00Entry)entry; + using (var input = new MemoryStream (g00arc.Bitmap)) + using (var reader = new BinaryReader (input)) + { + input.Position = g00ent.Offset; + int tile_type = reader.ReadUInt16(); + int count = reader.ReadUInt16(); + if (tile_type != 1) + throw new InvalidFormatException(); + input.Seek (0x70, SeekOrigin.Current); + int dst_stride = (int)g00arc.ImageInfo.Width * 4; + var pixels = new byte[(int)g00arc.ImageInfo.Height * dst_stride]; + for (int i = 0; i < count; ++i) + { + int tile_x = reader.ReadUInt16(); + int tile_y = reader.ReadUInt16(); + reader.ReadInt16(); + int tile_width = reader.ReadUInt16(); + int tile_height = reader.ReadUInt16(); + input.Seek (0x52, SeekOrigin.Current); + + tile_x += g00ent.X; + tile_y += g00ent.Y; + if (tile_x + tile_width > g00arc.ImageInfo.Width || + tile_y + tile_height > g00arc.ImageInfo.Height) + throw new InvalidFormatException(); + int dst = tile_y * dst_stride + tile_x * 4; + int tile_stride = tile_width * 4; + for (int row = 0; row < tile_height; ++row) + { + reader.Read (pixels, dst, tile_stride); + dst += dst_stride; + } + } + return TgaStream.Create (g00arc.ImageInfo, pixels); + } + } + } +} diff --git a/ArcFormats/RealLive/ImageG00.cs b/ArcFormats/RealLive/ImageG00.cs index c0065b3e..f520ef11 100644 --- a/ArcFormats/RealLive/ImageG00.cs +++ b/ArcFormats/RealLive/ImageG00.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Linq; using System.Windows.Media; using System.Windows.Media.Imaging; using GameRes.Utility; @@ -107,7 +108,6 @@ namespace GameRes.Formats.RealLive int m_width; int m_height; int m_type; - int m_bytes_pp; public byte[] Data { get { return m_output; } } public PixelFormat Format { get; private set; } @@ -134,15 +134,13 @@ namespace GameRes.Formats.RealLive void UnpackV0 () { - m_bytes_pp = 3; - m_output = LzDecompress (1); + m_output = LzDecompress (m_input, 1, 3); Format = PixelFormats.Bgr24; } void UnpackV1 () { - m_bytes_pp = 1; - m_output = LzDecompress (2); + m_output = LzDecompress (m_input, 2, 1); int colors = LittleEndian.ToUInt16 (m_output, 0); int src = 2; var palette = new Color[colors]; @@ -158,7 +156,6 @@ namespace GameRes.Formats.RealLive void UnpackV2 () { - m_bytes_pp = 1; Format = PixelFormats.Bgra32; int tile_count = m_input.ReadInt32(); var tiles = new List (tile_count); @@ -170,7 +167,7 @@ namespace GameRes.Formats.RealLive tiles.Add (tile); m_input.BaseStream.Seek (0x10, SeekOrigin.Current); } - using (var input = new MemoryStream (LzDecompress (2))) + using (var input = new MemoryStream (LzDecompress (m_input, 2, 1))) using (var reader = new BinaryReader (input)) { if (reader.ReadInt32() != tile_count) @@ -182,45 +179,42 @@ namespace GameRes.Formats.RealLive tiles[i].Offset = reader.ReadUInt32(); tiles[i].Length = reader.ReadInt32(); } - foreach (var tile in tiles) - { - if (0 == tile.Length) - continue; - input.Position = tile.Offset; - int tile_type = reader.ReadUInt16(); - int count = reader.ReadUInt16(); - if (tile_type != 1) - throw new InvalidFormatException(); - input.Seek (0x70, SeekOrigin.Current); - for (int i = 0; i < count; ++i) - { - int tile_x = reader.ReadUInt16(); - int tile_y = reader.ReadUInt16(); - reader.ReadInt16(); - int tile_width = reader.ReadUInt16(); - int tile_height = reader.ReadUInt16(); - input.Seek (0x52, SeekOrigin.Current); + var tile = tiles.First (t => t.Length != 0); - tile_x += tile.X; - tile_y += tile.Y; - if (tile_x + tile_width > m_width || tile_y + tile_height > m_height) - throw new InvalidFormatException(); - int dst = tile_y * dst_stride + tile_x * 4; - int tile_stride = tile_width * 4; - for (int row = 0; row < tile_height; ++row) - { - reader.Read (m_output, dst, tile_stride); - dst += dst_stride; - } + input.Position = tile.Offset; + int tile_type = reader.ReadUInt16(); + int count = reader.ReadUInt16(); + if (tile_type != 1) + throw new InvalidFormatException(); + input.Seek (0x70, SeekOrigin.Current); + for (int i = 0; i < count; ++i) + { + int tile_x = reader.ReadUInt16(); + int tile_y = reader.ReadUInt16(); + reader.ReadInt16(); + int tile_width = reader.ReadUInt16(); + int tile_height = reader.ReadUInt16(); + input.Seek (0x52, SeekOrigin.Current); + + tile_x += tile.X; + tile_y += tile.Y; + if (tile_x + tile_width > m_width || tile_y + tile_height > m_height) + throw new InvalidFormatException(); + int dst = tile_y * dst_stride + tile_x * 4; + int tile_stride = tile_width * 4; + for (int row = 0; row < tile_height; ++row) + { + reader.Read (m_output, dst, tile_stride); + dst += dst_stride; } } } } - byte[] LzDecompress (int min_count) + public static byte[] LzDecompress (BinaryReader input, int min_count, int bytes_pp) { - int packed_size = m_input.ReadInt32() - 8; - int output_size = m_input.ReadInt32(); + int packed_size = input.ReadInt32() - 8; + int output_size = input.ReadInt32(); var output = new byte[output_size]; int dst = 0; int bits = 2; @@ -229,25 +223,25 @@ namespace GameRes.Formats.RealLive bits >>= 1; if (1 == bits) { - bits = m_input.ReadByte() | 0x100; + bits = input.ReadByte() | 0x100; --packed_size; } if (0 != (bits & 1)) { - m_input.Read (output, dst, m_bytes_pp); - dst += m_bytes_pp; - packed_size -= m_bytes_pp; + input.Read (output, dst, bytes_pp); + dst += bytes_pp; + packed_size -= bytes_pp; } else { if (packed_size < 2) break; - int offset = m_input.ReadUInt16(); + int offset = input.ReadUInt16(); packed_size -= 2; int count = (offset & 0xF) + min_count; offset >>= 4; - offset *= m_bytes_pp; - count *= m_bytes_pp; + offset *= bytes_pp; + count *= bytes_pp; Binary.CopyOverlapped (output, dst-offset, dst, count); dst += count; }