From 1ad7cfde74f424695bfea6a26deab5f2c3493d72 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 24 Jun 2016 21:58:47 +0400 Subject: [PATCH] implemented Aoi AGF images. --- ArcFormats/Aoi/ArcBOX.cs | 51 +++++++++ ArcFormats/Aoi/ImageAGF.cs | 201 +++++++++++++++++++++++++++++++++++ ArcFormats/ArcFormats.csproj | 1 + supported.html | 3 +- 4 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 ArcFormats/Aoi/ImageAGF.cs diff --git a/ArcFormats/Aoi/ArcBOX.cs b/ArcFormats/Aoi/ArcBOX.cs index 11c90083..6be4a5b9 100644 --- a/ArcFormats/Aoi/ArcBOX.cs +++ b/ArcFormats/Aoi/ArcBOX.cs @@ -27,6 +27,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; using System.Security.Cryptography; +using System.Text; using GameRes.Utility; namespace GameRes.Formats.Aoi @@ -149,6 +150,11 @@ namespace GameRes.Formats.Aoi public override bool IsHierarchic { get { return false; } } public override bool CanCreate { get { return false; } } + public AoiMyOpener () + { + Extensions = new string[] { "box" }; + } + public override ArcFile TryOpen (ArcView file) { if (!file.View.AsciiEqual (4, "Y01\0")) @@ -206,4 +212,49 @@ namespace GameRes.Formats.Aoi return (byte)key; } } + + [Export(typeof(ArchiveFormat))] + public class AoiMyUnicodeOpener : AoiMyOpener + { + public override string Tag { get { return "AOIMY/UNICODE"; } } + public override string Description { get { return "Aoi engine script archive"; } } + public override uint Signature { get { return 0x004F0041; } } // 'A O ' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + var name_buffer = new byte[0x20]; + file.View.Read (0, name_buffer, 0, 0x16); + if ("AOIMY01\0" != Encoding.Unicode.GetString (name_buffer, 0, 0x10)) + return null; + int count = Binary.BigEndian (file.View.ReadInt32 (0x10)); + if (!IsSaneCount (count)) + return null; + int index_offset = 0x18; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + if (0x20 != file.View.Read (index_offset, name_buffer, 0, 0x20)) + return null; + int n; + for (n = 0; n < name_buffer.Length; n += 2) + if (0 == name_buffer[n] && 0 == name_buffer[n+1]) + break; + if (0 == n) + return null; + var entry = new Entry { + Name = Encoding.Unicode.GetString (name_buffer, 0, n), + Type = "script", + Offset = Binary.BigEndian (file.View.ReadUInt32 (index_offset+0x20)), + Size = Binary.BigEndian (file.View.ReadUInt32 (index_offset+0x24)), + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 0x28; + } + return new ArcFile (file, this, dir); + } + } } diff --git a/ArcFormats/Aoi/ImageAGF.cs b/ArcFormats/Aoi/ImageAGF.cs new file mode 100644 index 00000000..fb957ba3 --- /dev/null +++ b/ArcFormats/Aoi/ImageAGF.cs @@ -0,0 +1,201 @@ +//! \file ImageAGF.cs +//! \date Thu Jun 23 16:23:03 2016 +//! \brief Aoi engine 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; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.Aoi +{ + internal class AgfMetaData : ImageMetaData + { + public int Version; + public uint DataOffset; + public uint Flags; + public uint BaseNameOffset; + } + + [Export(typeof(ImageFormat))] + public class AgfFormat : ImageFormat + { + public override string Tag { get { return "AGF/AOI"; } } + public override string Description { get { return "Aoi engine image format"; } } + public override uint Signature { get { return 0x464741; } } // 'AGF' + + public AgfFormat () + { + Extensions = new string[] { "agf" }; + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x80]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + int version = LittleEndian.ToInt32 (header, 4); + if (version != 1 && version != 2) + return null; + var info = new AgfMetaData + { + Width = LittleEndian.ToUInt32 (header, 0x1C), + Height = LittleEndian.ToUInt32 (header, 0x20), + BPP = 32, + Version = version, + }; + if (1 == version) + { + info.DataOffset = LittleEndian.ToUInt32 (header, 0x0C); + } + else + { + info.DataOffset = LittleEndian.ToUInt32 (header, 0x10); + info.Flags = LittleEndian.ToUInt32 (header, 0x54); + info.BaseNameOffset = LittleEndian.ToUInt32 (header, 0x6C); + } + return info; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = (AgfMetaData)info; + var pixels = new byte[meta.Width * meta.Height * 4]; + int dst = 0; + stream.Position = meta.DataOffset; + using (var input = new BinaryReader (stream, Encoding.Unicode, true)) + { + while (dst < pixels.Length) + { + uint op = input.ReadUInt32(); + int count = (int)(op >> 8); + switch (op & 0xFF) + { + case 1: + count *= 4; + input.Read (pixels, dst, count); + break; + + case 2: + input.Read (pixels, dst, 4); + count *= 4; + Binary.CopyOverlapped (pixels, dst, dst+4, count - 4); + break; + + case 3: + int chunk_size = (count >> 8) * 4; + count = (count & 0xFF) * chunk_size; + input.Read (pixels, dst, chunk_size); + Binary.CopyOverlapped (pixels, dst, dst + chunk_size, count - chunk_size); + break; + + case 4: + int offset = (count & 0xFFF) * 4; + count = (count >> 12) * 4; + Binary.CopyOverlapped (pixels, dst - offset, dst, count); + break; + + case 5: + count = (count >> 8) & 0xFF; + input.BaseStream.Seek ((count - count / 4) * 4, SeekOrigin.Current); + count *= 4; + break; + + default: + throw new InvalidFormatException(); + } + dst += count; + } + if (0 != (meta.Flags & 0x10) && 0 != meta.BaseNameOffset) + { + try + { + var base_name = ReadBaseName (input, meta); + if (VFS.FileExists (base_name)) + { + using (var base_file = VFS.OpenSeekableStream (base_name)) + { + var base_image = Read (base_name, base_file); + BlendImage (meta, pixels, base_image.Bitmap); + } + } + } + catch (Exception X) + { + Trace.WriteLine (string.Format ("{0}: baseline image read error: {1}", + meta.FileName, X.Message), "[AGF]"); + } + } + } + return ImageData.Create (info, PixelFormats.Bgra32, null, pixels); + } + + string ReadBaseName (BinaryReader input, AgfMetaData info) + { + input.BaseStream.Position = info.DataOffset + info.BaseNameOffset; + var name = new StringBuilder(); + for (;;) + { + char c = input.ReadChar(); + if (0 == c) + break; + name.Append (c); + } + var dir_name = VFS.GetDirectoryName (info.FileName); + return VFS.CombinePath (dir_name, name.ToString()); + } + + void BlendImage (AgfMetaData info, byte[] overlay, BitmapSource bitmap) + { + if (bitmap.PixelWidth != info.Width || bitmap.PixelHeight != info.Height) + return; + if (bitmap.Format.BitsPerPixel != 32) + bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgra32, null, 0); + + int stride = bitmap.PixelWidth * 4; + byte[] pixels = new byte[stride * bitmap.PixelHeight]; + bitmap.CopyPixels (pixels, stride, 0); + for (int i = 0; i < pixels.Length; i += 4) + { + int pix = overlay[i] | overlay[i+1] | overlay[i+2] | overlay[i+3]; + if (0 == pix) + { + overlay[i ] = pixels[i ]; + overlay[i+1] = pixels[i+1]; + overlay[i+2] = pixels[i+2]; + overlay[i+3] = pixels[i+3]; + } + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("AgfFormat.Write not implemented"); + } + } +} diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index c53b0e45..7b677b5c 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -71,6 +71,7 @@ WidgetAGS.xaml + diff --git a/supported.html b/supported.html index 1f234e30..d24f8b45 100644 --- a/supported.html +++ b/supported.html @@ -783,7 +783,7 @@ Majidashi! Royale ~Finish wa Watashi no Naka de~
MILK Junkies
*.zbmamp_No -*.vfsVFNoAoi +*.vfsVFNoAoi Alfred Gakuen Mamono Daitai
Brown Doori Sanbanme
Daisounan
@@ -795,6 +795,7 @@ Planet Dragon
*.iphRIFF....IPH fmtNo *.aogAoiOggNo *.boxAOIBOX5
AOIBOX7
AOIBX10
AOIBX12
AOIMY01No +*.agfAGFNo *.gd+*.dll-NoXuse Eien no Aselia -The Spirit of Eternity Sword-