//! \file ImageCRXD.cs //! \date Thu Oct 13 14:18:47 2016 //! \brief Circus differential image format. // // 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.ComponentModel.Composition; using System.Drawing; using System.IO; using GameRes.Utility; namespace GameRes.Formats.Circus { internal class CrxdMetaData : ImageMetaData { public string BaseFileName; public uint BaseOffset; public CrxMetaData DiffInfo; public uint DiffOffset; } [Export(typeof(ImageFormat))] public class CrxdFormat : CrxFormat { public override string Tag { get { return "CRXD"; } } public override string Description { get { return "Circus differential image format"; } } public override uint Signature { get { return 0x44585243; } } // 'CRXD' public CrxdFormat () { Extensions = new string[] { "crx" }; } public override ImageMetaData ReadMetaData (Stream stream) { var header = new byte[0x24]; if (header.Length != stream.Read (header, 0, header.Length)) return null; CrxdMetaData info = null; if (Binary.AsciiEqual (header, 0x20, "CRXJ")) { stream.Position = 0x28; stream.Read (header, 0, 4); uint diff_offset = LittleEndian.ToUInt32 (header, 0); using (var crx = OpenByOffset (diff_offset)) { if (null == crx) return null; info = ReadMetaData (crx) as CrxdMetaData; if (info != null) info.DiffOffset = diff_offset; } } else if (Binary.AsciiEqual (header, 0x20, "CRXG")) { using (var crx = new StreamRegion (stream, 0x20, true)) { var diff_info = base.ReadMetaData (crx) as CrxMetaData; if (null == diff_info) return null; info = new CrxdMetaData { Width = diff_info.Width, Height = diff_info.Height, OffsetX = diff_info.OffsetX, OffsetY = diff_info.OffsetY, BPP = diff_info.BPP, DiffInfo = diff_info, DiffOffset = 0, }; } } if (info != null) { info.BaseOffset = LittleEndian.ToUInt32 (header, 8); info.BaseFileName = Binary.GetCString (header, 0xC, 0x14); } return info; } Stream OpenByOffset (uint offset) { var vfs = VFS.Top as ArchiveFileSystem; if (null == vfs) return null; var arc = vfs.Source as CrmArchive; if (null == arc) return null; return arc.OpenByOffset (offset); } Stream OpenDiffStream (Stream diff, CrxdMetaData info) { if (0 == info.DiffOffset) return new StreamRegion (diff, 0x20, true); diff = OpenByOffset (info.DiffOffset); if (null == diff) throw new FileNotFoundException ("Referenced diff image not found"); return new StreamRegion (diff, 0x20); } public override ImageData Read (Stream stream, ImageMetaData info) { var meta = (CrxdMetaData)info; Stream base_file = OpenByOffset (meta.BaseOffset); if (null == base_file) { var dir_name = VFS.GetDirectoryName (meta.FileName); var name = VFS.CombinePath (dir_name, meta.BaseFileName); if (!VFS.FileExists (name)) throw new FileNotFoundException ("Base image not found", meta.BaseFileName); base_file = VFS.OpenSeekableStream (name); } using (base_file) { var base_info = base.ReadMetaData (base_file) as CrxMetaData; if (null == base_info || base_info.BPP != info.BPP) throw new InvalidFormatException ("Invalid base image"); using (var reader = new Reader (base_file, base_info)) using (var crx = OpenDiffStream (stream, meta)) using (var diff_reader = new Reader (crx, meta.DiffInfo)) { reader.Unpack (true); diff_reader.Unpack (true); var diff_rect = new Rectangle (meta.OffsetX, meta.OffsetY, (int)meta.Width, (int)meta.Height); var base_rect = new Rectangle (base_info.OffsetX, base_info.OffsetY, (int)base_info.Width, (int)base_info.Height); diff_rect = Rectangle.Intersect (diff_rect, base_rect); if (diff_rect.IsEmpty) throw new InvalidFormatException ("Empty diff region"); int pixel_size = base_info.BPP / 8; int x = diff_rect.X - base_rect.X; int y = diff_rect.Y - base_rect.Y; int dst = y * reader.Stride + pixel_size * x; var image = reader.Data; int dx = diff_rect.X - meta.OffsetX; int dy = diff_rect.Y - meta.OffsetY; int src = dy * diff_reader.Stride + pixel_size * dx; var diff = diff_reader.Data; int blend_stride = diff_rect.Width * pixel_size; for (int row = 0; row < diff_rect.Height; ++row) { for (int i = 0; i < blend_stride; i += pixel_size) { image[dst+i ] += diff[src+i]; image[dst+i+1] += diff[src+i+1]; image[dst+i+2] += diff[src+i+2]; if (4 == pixel_size) image[dst+i+3] -= diff[src+i+3]; } dst += reader.Stride; src += diff_reader.Stride; } return ImageData.Create (base_info, reader.Format, reader.Palette, image, reader.Stride); } } } public override void Write (Stream file, ImageData image) { throw new System.NotImplementedException ("CrxdFormat.Write not implemented"); } } }