From 44deae320c5f4caaa28c090d578feceac532fa14 Mon Sep 17 00:00:00 2001 From: morkt Date: Thu, 13 Oct 2016 14:49:29 +0400 Subject: [PATCH] implemented differential CRX images (#35) --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/Circus/ArcCRM.cs | 31 +++++- ArcFormats/Circus/ImageCRX.cs | 3 +- ArcFormats/Circus/ImageCRXD.cs | 188 +++++++++++++++++++++++++++++++++ supported.html | 5 +- 5 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 ArcFormats/Circus/ImageCRXD.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 83cf294f..5051b787 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -95,6 +95,7 @@ + diff --git a/ArcFormats/Circus/ArcCRM.cs b/ArcFormats/Circus/ArcCRM.cs index 2bbdc561..96868c2d 100644 --- a/ArcFormats/Circus/ArcCRM.cs +++ b/ArcFormats/Circus/ArcCRM.cs @@ -25,10 +25,29 @@ using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Linq; +using System.IO; namespace GameRes.Formats.Circus { + internal class CrmArchive : ArcFile + { + readonly IDictionary OffsetMap; + + public CrmArchive (ArcView arc, ArchiveFormat impl, ICollection dir, IDictionary offset_map) + : base (arc, impl, dir) + { + OffsetMap = offset_map; + } + + internal Stream OpenByOffset (uint offset) + { + Entry entry; + if (!OffsetMap.TryGetValue (offset, out entry)) + throw new FileNotFoundException(); + return OpenEntry (entry); + } + } + [Export(typeof(ArchiveFormat))] public class CrmOpener : ArchiveFormat { @@ -44,6 +63,7 @@ namespace GameRes.Formats.Circus if (!IsSaneCount (count)) return null; + var offset_map = new SortedDictionary(); int index_offset = 0x10; var dir = new List (count); for (int i = 0; i < count; ++i) @@ -53,25 +73,26 @@ namespace GameRes.Formats.Circus var entry = FormatCatalog.Instance.Create (name); entry.Offset = offset; dir.Add (entry); + offset_map[offset] = entry; index_offset += 0x20; } - using (var iterator = dir.OrderBy (e => e.Offset).GetEnumerator()) + using (var iterator = offset_map.GetEnumerator()) { if (iterator.MoveNext()) { for (;;) { - var entry = iterator.Current; + var entry = iterator.Current.Value; if (!iterator.MoveNext()) { entry.Size = (uint)(file.MaxOffset - entry.Offset); break; } - entry.Size = (uint)(iterator.Current.Offset - entry.Offset); + entry.Size = (uint)(iterator.Current.Key - entry.Offset); } } } - return new ArcFile (file, this, dir); + return new CrmArchive (file, this, dir, offset_map); } } } diff --git a/ArcFormats/Circus/ImageCRX.cs b/ArcFormats/Circus/ImageCRX.cs index 8a1302b8..4719342f 100644 --- a/ArcFormats/Circus/ImageCRX.cs +++ b/ArcFormats/Circus/ImageCRX.cs @@ -26,7 +26,6 @@ using System; using System.ComponentModel.Composition; using System.IO; -using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using GameRes.Compression; @@ -185,6 +184,7 @@ namespace GameRes.Formats.Circus int g = m_output[pixel+2]; int r = m_output[pixel+3]; + /* if (alpha != alpha_flip) { b += (w & 1) + shift; @@ -205,6 +205,7 @@ namespace GameRes.Formats.Circus else if (r > 0xff) r = 0xff; } + */ m_output[pixel] = (byte)b; m_output[pixel+1] = (byte)g; m_output[pixel+2] = (byte)r; diff --git a/ArcFormats/Circus/ImageCRXD.cs b/ArcFormats/Circus/ImageCRXD.cs new file mode 100644 index 00000000..9d0244fe --- /dev/null +++ b/ArcFormats/Circus/ImageCRXD.cs @@ -0,0 +1,188 @@ +//! \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)) + { + reader.Unpack(); + using (var crx = OpenDiffStream (stream, meta)) + using (var diff_reader = new Reader (crx, meta.DiffInfo)) + { + diff_reader.Unpack(); + 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"); + } + } +} diff --git a/supported.html b/supported.html index df8f4c20..5b8b09e1 100644 --- a/supported.html +++ b/supported.html @@ -226,6 +226,7 @@ Yume Miru Kusuri
*.pnaPNAPNo *.xflLBYesLiar-soft Angel Bullet
+Divus Rabies
Fairytale Requiem
Love Negotiator
Sekien no Inganock
@@ -609,13 +610,15 @@ Soshite Ashita no Sekai yori
*.bABMP7
abmp10
abmp11No *.pngDPNGNo -*.dat-NoCircus +*.dat-NoCircus +D.S. -Dal Segno-
Infantaria XP
Maid no Yakata ~Zetsubou Hen~
RPG Gakuen
*.pck-No *.crxCRXGNo +*.crmCRXBNo *.pcmXPCMNo *.pakCHERRY PACK 2.0
CHERRY PACK 3.0
-NoCherry Double