From d64fec9cfb53f70ba0afff26fde9ef353f21e283 Mon Sep 17 00:00:00 2001 From: MkfsSion Date: Sun, 9 Aug 2026 14:29:54 +0800 Subject: [PATCH] ArcFormats: Support Silky's MMO image format * Tested game: https://vndb.org/v449 Signed-off-by: MkfsSion --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/Silky/ImageAKB.cs | 26 ++++++++++++++++++-- ArcFormats/Silky/ImageMMO.cs | 46 ++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 ArcFormats/Silky/ImageMMO.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index c5a51c084..e154c3596 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -281,6 +281,7 @@ + diff --git a/ArcFormats/Silky/ImageAKB.cs b/ArcFormats/Silky/ImageAKB.cs index a6f28da15..b1186ff3f 100644 --- a/ArcFormats/Silky/ImageAKB.cs +++ b/ArcFormats/Silky/ImageAKB.cs @@ -40,6 +40,7 @@ internal class AkbMetaData : ImageMetaData public uint Flags; public string BaseFileName; public uint DataOffset; + public bool TopDownOrder; } [Export(typeof(ImageFormat))] @@ -150,13 +151,34 @@ public byte[] Unpack (byte[] background = null) var pixels = new byte[m_info.InnerHeight * inner_stride]; using (var lz = new LzssStream (m_input, LzssMode.Decompress, true)) { - for (int pos = pixels.Length - inner_stride; pos >= 0; pos -= inner_stride) + if (m_info.TopDownOrder) { - if (inner_stride != lz.Read (pixels, pos, inner_stride)) + if (lz.Read(pixels, 0, pixels.Length) != pixels.Length) + { throw new InvalidFormatException(); + } + } + else + { + for (int pos = pixels.Length - inner_stride; pos >= 0; pos -= inner_stride) + { + if (inner_stride != lz.Read (pixels, pos, inner_stride)) + throw new InvalidFormatException(); + } } + } RestoreDelta (pixels, inner_stride); + if (m_info.TopDownOrder) + { + var tmp_stride = new byte[inner_stride]; + for (int i = 0, j = m_info.InnerHeight - 1; i < j; ++i, --j) + { + Buffer.BlockCopy(pixels, j * inner_stride, tmp_stride, 0, inner_stride); + Buffer.BlockCopy(pixels, i * inner_stride, pixels, j * inner_stride, inner_stride); + Buffer.BlockCopy(tmp_stride, 0, pixels, i * inner_stride, inner_stride); + } + } if (null == background && m_info.InnerWidth == m_info.Width && m_info.InnerHeight == m_info.Height) return pixels; diff --git a/ArcFormats/Silky/ImageMMO.cs b/ArcFormats/Silky/ImageMMO.cs new file mode 100644 index 000000000..0846f0687 --- /dev/null +++ b/ArcFormats/Silky/ImageMMO.cs @@ -0,0 +1,46 @@ +using System.ComponentModel.Composition; + +// [070223][Silky's] Himekishi Angelica ~Anata tte, Hontou ni Saitei no Kuzu Da wa!~ + +namespace GameRes.Formats.Silky +{ + [Export(typeof(ImageFormat))] + public class MmoFormat : AkbFormat + { + public override string Tag => "MMO"; + public override string Description => "Silky's MMO image format"; + public override uint Signature => 0x204F4D4D; // 'MMO ' + + public MmoFormat() + { + Signatures = new[] { Signature }; + } + + public override ImageMetaData ReadMetaData(IBinaryStream file) + { + file.Position += 4; + var metaData = new AkbMetaData(); + metaData.TopDownOrder = true; + metaData.Flags = 0; + metaData.OffsetX = file.ReadInt32(); + metaData.OffsetY = file.ReadInt32(); + metaData.InnerWidth = file.ReadInt32() - metaData.OffsetX; + metaData.InnerHeight = file.ReadInt32() - metaData.OffsetY; + // Not sure if there are any other pixel encoding + metaData.BPP = 24; + // Skip 8 byte unknown field + file.Position += 8; + metaData.Width = file.ReadUInt32(); + metaData.Height = file.ReadUInt32(); + // Skip another 4 byte unknown field + file.Position += 4; + metaData.DataOffset = (uint)file.Position; + // FIXME: Implement reliable way to detect difference image and find the corresponding base image name Find base image + // Unlike AKB format, MMO format has neither mark for difference image nor the name of its base image file embedded. + // Also, there doesn't seem to have a constant naming pattern even in the same game. + metaData.BaseFileName = null; + metaData.Background = new byte[4]; + return metaData; + } + } +} \ No newline at end of file