From f90e2e851cfcf56396480caba2cf4c7ebbac6190 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 11 Oct 2023 19:03:45 +0400 Subject: [PATCH] updated legacy formats. (BIZ): moved to Adviz folder. (GIZ, GIZ2, BIZ2, PR1): new PC-98 image formats. (DATA, BGM, SED): MyHarvest resource formats. (HTF): compressed image format. (PAK): Mina resource archives. (GCmp): read palette from external resource. (NCG): Nekotaro image format. (BND, TCZ, TSZ): Ponytail Soft PC-98 formats. (NOR): Sophia resource archive. (SDA, PLA): Squadra D resource archives. (UCA): added checks to avoid false positives. --- Legacy/Adviz/ImageBIZ.cs | 366 ++++++++++++++++ .../ImageBIZ.cs => Adviz/ImageBIZ2.cs} | 44 +- Legacy/Adviz/ImageGIZ.cs | 309 ++++++++++++++ Legacy/Adviz/ImageGIZ2.cs | 231 ++++++++++ Legacy/Discovery/ImageAN1.cs | 80 ++++ Legacy/Discovery/ImagePR1.cs | 343 +++++++++++++++ Legacy/Harvest/ArcDAT.cs | 65 +++ Legacy/Harvest/AudioBGM.cs | 56 +++ Legacy/Harvest/AudioSED.cs | 61 +++ Legacy/Harvest/ImageUNH.cs | 96 +++++ Legacy/Jam/ImageHTF.cs | 69 +++ Legacy/Legacy.csproj | 20 +- Legacy/Mina/ArcPAK.cs | 299 +++++++++++++ Legacy/Nekotaro/ArcNSC.cs | 1 + Legacy/Nekotaro/ImageGCmp.cs | 394 ++++++------------ Legacy/Nekotaro/ImageNCG.cs | 293 +++++++++++++ Legacy/Ponytail/ArcBND.cs | 125 ++++++ Legacy/Ponytail/ImageTCZ.cs | 212 ++++++++++ Legacy/Ponytail/ImageTSZ.cs | 330 +++++++++++++++ Legacy/Properties/AssemblyInfo.cs | 4 +- Legacy/Sophia/ArcNOR.cs | 146 +++++++ Legacy/SquadraD/ArcPLA.cs | 114 +++++ Legacy/SquadraD/ArcSDA.cs | 121 ++++++ Legacy/WestGate/ArcUCA.cs | 4 +- 24 files changed, 3504 insertions(+), 279 deletions(-) create mode 100644 Legacy/Adviz/ImageBIZ.cs rename Legacy/{Sorciere/ImageBIZ.cs => Adviz/ImageBIZ2.cs} (56%) create mode 100644 Legacy/Adviz/ImageGIZ.cs create mode 100644 Legacy/Adviz/ImageGIZ2.cs create mode 100644 Legacy/Discovery/ImageAN1.cs create mode 100644 Legacy/Discovery/ImagePR1.cs create mode 100644 Legacy/Harvest/ArcDAT.cs create mode 100644 Legacy/Harvest/AudioBGM.cs create mode 100644 Legacy/Harvest/AudioSED.cs create mode 100644 Legacy/Harvest/ImageUNH.cs create mode 100644 Legacy/Jam/ImageHTF.cs create mode 100644 Legacy/Mina/ArcPAK.cs create mode 100644 Legacy/Nekotaro/ImageNCG.cs create mode 100644 Legacy/Ponytail/ArcBND.cs create mode 100644 Legacy/Ponytail/ImageTCZ.cs create mode 100644 Legacy/Ponytail/ImageTSZ.cs create mode 100644 Legacy/Sophia/ArcNOR.cs create mode 100644 Legacy/SquadraD/ArcPLA.cs create mode 100644 Legacy/SquadraD/ArcSDA.cs diff --git a/Legacy/Adviz/ImageBIZ.cs b/Legacy/Adviz/ImageBIZ.cs new file mode 100644 index 00000000..ed6c8bae --- /dev/null +++ b/Legacy/Adviz/ImageBIZ.cs @@ -0,0 +1,366 @@ +//! \file ImageBIZ.cs +//! \date 2023 Sep 30 +//! \brief ADVIZ engine image format. +// +// Copyright (C) 2023 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text.RegularExpressions; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [970829][Ange] Coin + +namespace GameRes.Formats.Adviz +{ + [Export(typeof(ImageFormat))] + public class BizFormat : ImageFormat + { + public override string Tag => "BIZ"; + public override string Description => "ADVIZ engine image format"; + public override uint Signature => 0; + + const byte DefaultKey = 0x39; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".BIZ")) + return null; + var header = file.ReadHeader (4); + uint width = header.ToUInt16 (0); + uint height = header.ToUInt16 (2); + if (width * height + 4 != file.Length) + return null; + return new ImageMetaData { + Width = width, + Height = height, + BPP = 8, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var palette = ReadPalette (file.Name, 0x300, (pal, off) => ReadPalette (pal, off, 0x100, PaletteFormat.Rgb)); + if (null == palette) + throw new FileNotFoundException ("Unable to retrieve palette."); + file.Position = 4; + var pixels = file.ReadBytes (info.iWidth * info.iHeight); + byte key = DefaultKey; + for (int i = 0; i < pixels.Length; ++i) + { + pixels[i] ^= key; + key += pixels[i]; + } + return ImageData.CreateFlipped (info, PixelFormats.Indexed8, palette, pixels, info.iWidth); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("BizFormat.Write not implemented"); + } + + internal delegate BitmapPalette PaletteReader (ArcView file, int offset); + + static readonly Regex TachieRe = new Regex (@"^(T[^._]+_)[2-9][^.]*\.GIZ$", RegexOptions.Compiled); + + internal static BitmapPalette ReadPalette (string base_name, int pal_size, PaletteReader read_pal) + { + var dir_name = Path.GetDirectoryName (base_name); + var grp_tbl_name = Path.Combine (dir_name, @"..\GRP_TBL.SYS"); + var plt_tbl_name = Path.Combine (dir_name, @"..\PLT_TBL.SYS"); + if (!File.Exists (grp_tbl_name) || !File.Exists (plt_tbl_name)) + return null; + int index = 0; + uint grp_size = 0; + base_name = Path.GetFileName (base_name).ToUpperInvariant(); + var name = base_name; + var ext = Path.GetExtension (name).TrimStart('.'); + var match = TachieRe.Match (name); + if (match.Success) + name = match.Groups[1].Value + "1"; + else + name = Path.GetFileNameWithoutExtension (name); + if (name.Length < 8) + name += ' '; + using (var grp = new ArcView (grp_tbl_name)) + { + grp_size = (uint)grp.MaxOffset; + int pos = 0; + while (pos + 12 <= grp.MaxOffset) + { + if (grp.View.AsciiEqual (pos, name) && + grp.View.AsciiEqual (pos+8, ext)) + { + break; + } + ++index; + pos += 12; + } + if (pos >= grp.MaxOffset) + return null; + } + using (var pal = new ArcView (plt_tbl_name)) + { + uint plt_size = (uint)pal.MaxOffset; + var id = new GrpIdentifier (grp_size, plt_size); + IGrpMapper mapper; + if (!GrpMap.TryGetValue (id, out mapper)) + mapper = new DirectMapper(); + index = mapper.GetPaletteIndex (index, base_name); + int pal_offset = index * pal_size; + if (pal_offset + pal_size > pal.MaxOffset) + { + int count = (int)(pal.MaxOffset / pal_size) - 1; + pal_offset = count * pal_size; + } + return read_pal (pal, pal_offset); + } + } + + static readonly Dictionary GrpMap = new Dictionary { + { new GrpIdentifier (1584, 139008), new GrpShiftMapper (52) }, + { new GrpIdentifier (2160, 12288), + new GrpNameMapper { NameMap = new Dictionary { + { "BG01.BIZ", 14 }, + { "BG02.BIZ", 8 }, + { "BG03.BIZ", 8 }, + { "BG04.BIZ", 8 }, + { "BG05.BIZ", 8 }, + { "BG06.BIZ", 8 }, + { "BG07.BIZ", 8 }, + { "BG08.BIZ", 8 }, + { "BG09.BIZ", 8 }, + { "BG10.BIZ", 8 }, + { "BG11.BIZ", 8 }, + { "BG12.BIZ", 8 }, + { "BG13.BIZ", 8 }, + { "BG14.BIZ", 8 }, + { "BG15.BIZ", 8 }, + { "BG16.BIZ", 8 }, + { "BG17.BIZ", 8 }, + { "BG18.BIZ", 8 }, + { "BG19.BIZ", 8 }, + { "CA01.BIZ", 12 }, + { "CA02.BIZ", 12 }, + { "CA03.BIZ", 4 }, + { "CA04.BIZ", 8 }, + { "CA05.BIZ", 8 }, + { "CA06.BIZ", 8 }, + { "CA07.BIZ", 8 }, + { "CA08.BIZ", 8 }, + { "CA09.BIZ", 8 }, + { "CA10.BIZ", 8 }, + { "CA11.BIZ", 8 }, + { "CA12.BIZ", 8 }, + { "CA13.BIZ", 8 }, + { "CA14.BIZ", 8 }, + { "CA15.BIZ", 8 }, + { "CA16.BIZ", 8 }, + { "CA17.BIZ", 8 }, + { "CA18.BIZ", 8 }, + { "CA19.BIZ", 8 }, + { "CA20.BIZ", 8 }, + { "CA21.BIZ", 8 }, + { "CA22.BIZ", 8 }, + { "CA23.BIZ", 8 }, + { "CA24.BIZ", 8 }, + { "CA25.BIZ", 8 }, + { "CA26.BIZ", 8 }, + { "CA27.BIZ", 8 }, + { "CA28.BIZ", 8 }, + { "CA29.BIZ", 8 }, + { "CA30.BIZ", 8 }, + { "CA31.BIZ", 8 }, + { "CA32.BIZ", 8 }, + { "CA33.BIZ", 8 }, + { "CA34.BIZ", 8 }, + { "CA35.BIZ", 8 }, + { "CA36.BIZ", 8 }, + { "CA37.BIZ", 8 }, + { "CA38.BIZ", 8 }, + { "CA39.BIZ", 8 }, + { "CA40.BIZ", 8 }, + { "CA41.BIZ", 8 }, + { "CA42.BIZ", 8 }, + { "CA43.BIZ", 8 }, + { "CA44.BIZ", 8 }, + { "CA45.BIZ", 8 }, + { "CA46.BIZ", 8 }, + { "CA47.BIZ", 8 }, + { "CA48.BIZ", 8 }, + { "CA49.BIZ", 8 }, + { "CA50.BIZ", 8 }, + { "CA51.BIZ", 8 }, + { "CA52.BIZ", 8 }, + { "CA53.BIZ", 8 }, + { "CA54.BIZ", 8 }, + { "CA55.BIZ", 8 }, + { "CA56.BIZ", 8 }, + { "CA57.BIZ", 8 }, + { "CA58.BIZ", 8 }, + { "CA59.BIZ", 8 }, + { "CA60.BIZ", 8 }, + { "E02.BIZ", 8 }, + { "E03.BIZ", 8 }, + { "E04.BIZ", 8 }, + { "E05.BIZ", 8 }, + { "E06.BIZ", 8 }, + { "E07.BIZ", 8 }, + { "E08.BIZ", 8 }, + { "E09.BIZ", 8 }, + { "E10.BIZ", 8 }, + { "E11.BIZ", 8 }, + { "E12.BIZ", 8 }, + { "E13.BIZ", 8 }, + { "E14.BIZ", 8 }, + { "E15.BIZ", 8 }, + { "E16.BIZ", 8 }, + { "E17.BIZ", 8 }, + { "E18.BIZ", 8 }, + { "END.BIZ", 6 }, + { "IPL.BIZ", 12 }, + { "S01.BIZ", 8 }, + { "S02.BIZ", 8 }, + { "S03.BIZ", 8 }, + { "S04.BIZ", 8 }, + { "S05.BIZ", 8 }, + { "S06.BIZ", 8 }, + { "S07.BIZ", 8 }, + { "S08.BIZ", 8 }, + { "S09.BIZ", 8 }, + { "S10.BIZ", 8 }, + { "S11.BIZ", 8 }, + { "S12.BIZ", 8 }, + { "S13.BIZ", 8 }, + { "S14.BIZ", 8 }, + { "S15.BIZ", 8 }, + { "S16.BIZ", 8 }, + { "S17.BIZ", 8 }, + { "S18.BIZ", 8 }, + { "S19.BIZ", 8 }, + { "S20.BIZ", 8 }, + { "S21.BIZ", 8 }, + { "S22.BIZ", 8 }, + { "S23.BIZ", 8 }, + { "S24.BIZ", 8 }, + { "S25.BIZ", 8 }, + { "S26.BIZ", 8 }, + { "S27.BIZ", 8 }, + { "S28.BIZ", 8 }, + { "S29.BIZ", 8 }, + { "S30.BIZ", 8 }, + { "S31.BIZ", 8 }, + { "S32.BIZ", 8 }, + { "S33.BIZ", 8 }, + { "S34.BIZ", 8 }, + { "S35.BIZ", 8 }, + { "S36.BIZ", 8 }, + { "S37.BIZ", 8 }, + { "S38.BIZ", 8 }, + { "S39.BIZ", 8 }, + { "S40.BIZ", 8 }, + { "S41.BIZ", 8 }, + { "S42.BIZ", 8 }, + { "S43.BIZ", 8 }, + { "S44.BIZ", 8 }, + { "S45.BIZ", 8 }, + { "S46.BIZ", 8 }, + { "S47.BIZ", 8 }, + { "S48.BIZ", 8 }, + { "S49.BIZ", 8 }, + { "T01.BIZ", 8 }, + { "T02.BIZ", 8 }, + { "T03.BIZ", 8 }, + { "WAKU1.BIZ", 8 }, + { "WAKU2.BIZ", 8 }, + } } }, + }; + } + + public struct GrpIdentifier + { + public uint GrpSize; + public uint PltSize; + + public GrpIdentifier (uint grp_size, uint plt_size) + { + GrpSize = grp_size; + PltSize = plt_size; + } + + public override int GetHashCode () + { + return (int)((GrpSize + 1) * (PltSize + 1)); + } + + public override bool Equals (object obj) + { + if (null == obj) + return false; + var other = (GrpIdentifier)obj; + return this.GrpSize == other.GrpSize && this.PltSize == other.PltSize; + } + } + + internal interface IGrpMapper + { + int GetPaletteIndex (int id, string name); + } + + internal class DirectMapper : IGrpMapper + { + public int GetPaletteIndex (int id, string name) + { + return id; + } + } + + internal class GrpShiftMapper : IGrpMapper + { + int m_shift; + + public GrpShiftMapper (int shift) + { + m_shift = shift; + } + + public int GetPaletteIndex (int id, string name) + { + return id + m_shift; + } + } + + internal class GrpNameMapper : IGrpMapper + { + public Dictionary NameMap; + + public int GetPaletteIndex (int id, string name) + { + int index; + if (NameMap.TryGetValue (name, out index)) + return index; + return id; + } + } +} diff --git a/Legacy/Sorciere/ImageBIZ.cs b/Legacy/Adviz/ImageBIZ2.cs similarity index 56% rename from Legacy/Sorciere/ImageBIZ.cs rename to Legacy/Adviz/ImageBIZ2.cs index 83260b74..a6a86fcc 100644 --- a/Legacy/Sorciere/ImageBIZ.cs +++ b/Legacy/Adviz/ImageBIZ2.cs @@ -1,6 +1,6 @@ -//! \file ImageBIZ.cs +//! \file ImageBIZ2.cs //! \date 2018 Feb 11 -//! \brief Sorciere compressed image. +//! \brief ADVIZ engine compressed image. // // Copyright (C) 2018 by morkt // @@ -29,14 +29,15 @@ using System.Windows.Media; using GameRes.Compression; // [000225][Sorciere] Karei +// [011012][Ange] Nyuunyuu -namespace GameRes.Formats.Sorciere +namespace GameRes.Formats.Adviz { [Export(typeof(ImageFormat))] - public class BizFormat : ImageFormat + public class Biz2Format : ImageFormat { - public override string Tag { get { return "BIZ"; } } - public override string Description { get { return "Sorciere compressed image"; } } + public override string Tag { get { return "BIZ/2"; } } + public override string Description { get { return "ADVIZ engine compressed image"; } } public override uint Signature { get { return 0x325A4942; } } // 'BIZ2' public override ImageMetaData ReadMetaData (IBinaryStream file) @@ -52,13 +53,34 @@ namespace GameRes.Formats.Sorciere public override ImageData Read (IBinaryStream file, ImageMetaData info) { file.Position = 8; - using (var input = new LzssStream (file.AsStream, LzssMode.Decompress, true)) + using (var lzss = new LzssStream (file.AsStream, LzssMode.Decompress, true)) + using (var input = new BinaryStream (lzss, file.Name)) { - int stride = (int)info.Width * 3; - var pixels = new byte[stride * (int)info.Height]; - if (pixels.Length != input.Read (pixels, 0, pixels.Length)) + int stride = info.iWidth * 3; + var rgb = new byte[stride * info.Height]; + if (rgb.Length != input.Read (rgb, 0, rgb.Length)) throw new InvalidFormatException(); - return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, pixels, stride); + if (input.PeekByte() != -1) // possible alpha channel + { + var alpha = input.ReadBytes (rgb.Length); + if (alpha.Length == rgb.Length) + { + int stride32bpp = info.iWidth * 4; + var rgba = new byte[stride32bpp * info.iHeight]; + int src = 0; + int dst = 0; + while (src < rgb.Length) + { + rgba[dst++] = rgb[src ]; + rgba[dst++] = rgb[src+1]; + rgba[dst++] = rgb[src+2]; + rgba[dst++] = alpha[src]; // presumably it's grayscale and R/G/B values are equal + src += 3; + } + return ImageData.CreateFlipped (info, PixelFormats.Bgra32, null, rgba, stride32bpp); + } + } + return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, rgb, stride); } } diff --git a/Legacy/Adviz/ImageGIZ.cs b/Legacy/Adviz/ImageGIZ.cs new file mode 100644 index 00000000..8e4da430 --- /dev/null +++ b/Legacy/Adviz/ImageGIZ.cs @@ -0,0 +1,309 @@ +//! \file ImageGIZ.cs +//! \date 2023 Oct 02 +//! \brief ADVIZ engine image format (PC-98). +// +// Copyright (C) 2023 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 GameRes.Utility; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [960830][Ange] Coin + +namespace GameRes.Formats.Adviz +{ + internal class GizMetaData : ImageMetaData + { + public byte RleCode; + public byte PlaneMap; + public bool HasPalette; + } + + [Export(typeof(ImageFormat))] + public class Giz3Format : ImageFormat + { + public override string Tag => "GIZ"; + public override string Description => "ADVIZ engine image format"; + public override uint Signature => 0x335A4947; // 'GIZ3' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + int xy = header.ToUInt16 (4); + return new GizMetaData { + Width = (uint)header.ToUInt16 (6) << 3, + Height = header.ToUInt16 (8), + OffsetX = (xy % 0x50) << 3, + OffsetY = xy / 0x50, + HasPalette = header[0xC] != 0, + PlaneMap = header[0xE], + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Giz3Reader (file, (GizMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Giz3Format.Write not implemented"); + } + } + + internal class Giz3Reader + { + IBinaryStream m_input; + GizMetaData m_info; + BitmapPalette m_palette; + int m_stride; + int m_output_stride; + + public Giz3Reader (IBinaryStream input, GizMetaData info) + { + m_input = input; + m_info = info; + } + + byte[] m_buffer; + byte[] m_output; + + public ImageData Unpack () + { + m_input.Position = 0x10; + if (m_info.HasPalette) + m_palette = ReadPalette(); + else + m_palette = BitmapPalettes.Gray16; // palette is stored somewhere else + long data_pos = m_input.Position; + ReadHuffmanTree(); + data_pos += m_dataOffset; + + m_bitCount = 1; + m_stride = m_info.iWidth >> 3; + m_buffer = new byte[0x2000]; + m_output_stride = m_info.iWidth >> 1; + m_output = new byte[m_output_stride * m_info.iHeight]; + m_input.Position = data_pos; + UnpackBits(); + return ImageData.Create (m_info, PixelFormats.Indexed4, m_palette, m_output, m_output_stride); + } + + int m_dataOffset; + int m_gizColumn; + int m_outputPos1; + int m_outputPos2; + + void UnpackBits () // sub_15426 + { + m_gizColumn = 0; + m_outputPos1 = 0; + m_outputPos2 = 0; + int dst = 0; + for (int x = 0; x < m_stride; ++x) + { + int src1 = m_outputPos1; + int src2 = 0; + for (int j = 0; j < 2; ++j) + { + src2 = m_outputPos1; + int plane_mask = 1; + for (int i = 0; i < 4; ++i) + { + if ((m_info.PlaneMap & plane_mask) == 0) + { + UnpackPlane (m_outputPos1); + } + plane_mask <<= 1; + m_outputPos1 += 0x800; + m_outputPos2 += 0x800; + } + m_gizColumn = (m_gizColumn + 1) & 3; + m_outputPos1 = m_gizColumn << 9; + m_outputPos2 = 0; + } + CopyPlanes (src1, src2, dst); + dst += 4; + } + } + + void CopyPlanes (int src1, int src2, int dst) + { + for (int y = 0; y < m_info.iHeight; ++y) + { + int b0 = m_buffer[src1+y ] << 4 | m_buffer[src2+y ]; + int b1 = m_buffer[src1+y+0x0800] << 4 | m_buffer[src2+y+0x0800]; + int b2 = m_buffer[src1+y+0x1000] << 4 | m_buffer[src2+y+0x1000]; + int b3 = m_buffer[src1+y+0x1800] << 4 | m_buffer[src2+y+0x1800]; + for (int j = 0; j < 8; j += 2) + { + byte px = (byte)((((b0 << j) & 0x80) >> 3) + | (((b1 << j) & 0x80) >> 2) + | (((b2 << j) & 0x80) >> 1) + | (((b3 << j) & 0x80) )); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + m_output[dst+j/2] = px; + } + dst += m_output_stride; + } + } + + int m_root; + ushort[] m_treeTable; + + void ReadHuffmanTree () + { + m_dataOffset = m_input.ReadUInt16(); + m_treeTable = new ushort[(m_dataOffset-2) * 2 / 3 + 1]; + int di = 0; + for (int si = 2; si + 2 < m_dataOffset; si += 3) + { + ushort bx = m_input.ReadUInt16(); + int ax = bx & 0xFFF; + if ((ax & 0x800) == 0) + ax = (ax - 2) >> 1; + m_treeTable[di++] = (ushort)ax; + ax = m_input.ReadUInt8() << 4; + ax |= bx >> 12; + if ((ax & 0x800) == 0) + ax = (ax - 2) >> 1; + m_treeTable[di++] = (ushort)ax; + } + m_root = di - 2; + } + + byte ReadToken () + { + int token = m_root; + do + { + if (GetNextBit()) + ++token; + token = m_treeTable[token]; + } + while ((token & 0x800) == 0); + return (byte)token; + } + + void UnpackPlane (int dst) + { + int y = 0; + while (y < m_info.iHeight) + { + byte ctl = ReadToken(); + if (ctl < 0x10) + { + m_buffer[dst++] = ctl; + ++y; + } + else + { + int count = ReadToken() + 2; + ctl -= 0x10; + switch (ctl) + { + case 0: + for (int i = 0; i < count; ++i) + m_buffer[dst+i] = 0; + break; + + case 1: + for (int i = 0; i < count; ++i) + m_buffer[dst+i] = 0xF; + break; + + case 2: + Binary.CopyOverlapped (m_buffer, dst-1, dst, count); + break; + + case 3: + Binary.CopyOverlapped (m_buffer, dst-2, dst, count); + break; + + case 4: + case 5: + case 6: + { + int off = (ctl - 3) << 11; + Binary.CopyOverlapped (m_buffer, dst-off, dst, count); + break; + } + case 7: + { + int src = dst - m_outputPos1; + int ax = (m_gizColumn - 1) & 3; + src += (ax << 9) + m_outputPos2; + Binary.CopyOverlapped (m_buffer, src, dst, count); + break; + } + case 8: + { + int src = dst - m_outputPos1; + int ax = (m_gizColumn - 2) & 3; + src += (ax << 9) + m_outputPos2; + Binary.CopyOverlapped (m_buffer, src, dst, count); + break; + } + } + dst += count; + y += count; + } + } + } + + BitmapPalette ReadPalette () + { + const int count = 16; + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + byte b = m_input.ReadUInt8(); + byte r = m_input.ReadUInt8(); + byte g = m_input.ReadUInt8(); + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + + int m_bitCount; + int m_bits; + + bool GetNextBit () + { + if (--m_bitCount == 0) + { + m_bits = m_input.ReadUInt16(); + m_bitCount = 16; + } + bool bit = (m_bits & 0x8000) != 0; + m_bits <<= 1; + return bit; + } + } +} diff --git a/Legacy/Adviz/ImageGIZ2.cs b/Legacy/Adviz/ImageGIZ2.cs new file mode 100644 index 00000000..7ceb287c --- /dev/null +++ b/Legacy/Adviz/ImageGIZ2.cs @@ -0,0 +1,231 @@ +//! \file ImageGIZ2.cs +//! \date 2023 Oct 02 +//! \brief ADVIZ engine image format (PC-98). +// +// Copyright (C) 2023 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 GameRes.Utility; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [951027][Ange] Leap Toki ni Sarawareta Shoujo + +namespace GameRes.Formats.Adviz +{ + [Export(typeof(ImageFormat))] + public class GizFormat : ImageFormat + { + public override string Tag => "GIZ/2"; + public override string Description => "ADVIZ engine image format"; + public override uint Signature => 0x325A4947; // 'GIZ2' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + int xy = header.ToUInt16 (4); + return new GizMetaData { + Width = (uint)header.ToUInt16 (6) << 3, + Height = header.ToUInt16 (8), + OffsetX = (xy % 0x50) << 3, + OffsetY = xy / 0x50, + RleCode = header[0xC], + PlaneMap = header[0xE], + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Giz2Reader (file, (GizMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GizFormat.Write not implemented"); + } + } + + internal class Giz2Reader + { + IBinaryStream m_input; + GizMetaData m_info; + BitmapPalette m_palette; + + public BitmapPalette Palette => m_palette; + + public Giz2Reader (IBinaryStream input, GizMetaData info) + { + m_input = input; + m_info = info; + } + + int m_stride; + byte[][] m_planes; + int m_output_stride; + byte[] m_output; + + public ImageData Unpack () + { + m_palette = BizFormat.ReadPalette (m_input.Name, 0x30, (pal, off) => ReadPalette (pal, off)); + if (null == m_palette) + { +// m_palette = BitmapPalettes.Gray16; + throw new FileNotFoundException ("Unable to retrieve palette."); + } + m_input.Position = 0x10; + m_stride = m_info.iWidth >> 3; + int plane_size = m_info.iHeight; + m_planes = new byte[][] { + new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size], + }; + m_output_stride = m_info.iWidth >> 1; + m_output = new byte[m_output_stride * m_info.iHeight]; + int dst = 0; + for (int x = 0; x < m_stride; ++x) + { + int plane_mask = 1; + for (int i = 0; i < 4; ++i) + { + if ((m_info.PlaneMap & plane_mask) == 0) + UnpackPlane (m_planes[i], 0); + plane_mask <<= 1; + } + CopyPlanes (dst); + dst += 4; + } + return ImageData.Create (m_info, PixelFormats.Indexed4, Palette, m_output, m_output_stride); + } + + bool UnpackPlane (byte[] output, int dst) + { + for (int y = 0; y < m_info.iHeight; ) + { + byte b = m_input.ReadUInt8(); + int ctl = (b - m_info.RleCode) & 0xFF; + if (2 == ctl) + { + output[dst++] = m_input.ReadUInt8(); + } + else if (ctl < 4) + { + if (0 == ctl) + b = 0; + else if (1 == ctl) + b = 0xFF; + else + b = m_input.ReadUInt8(); + int count = ((m_input.ReadUInt8() - 1) & 0xFF) + 1; + y += count; + while (count --> 0) + { + output[dst++] = b; + } + continue; + } + else if (ctl < 7) + { + byte b0 = m_input.ReadUInt8(); + byte b1 = m_input.ReadUInt8(); + int count; + if (4 == ctl) + { + count = ((b1 - 1) & 0x7F) + 1; + if (b1 < 0x80) + b1 = Binary.RotByteL (b0, 1); + else + b1 = Binary.RotByteR (b0, 1); + } + else if (5 == ctl) + { + count = ((b1 - 1) & 0x7F) + 1; + if (b1 < 0x80) + b1 = Binary.RotByteL (b0, 2); + else + b1 = Binary.RotByteR (b0, 2); + } + else + { + count = ((m_input.ReadUInt8() - 1) & 0xFF) + 1; + count *= 2; + } + y += count; + do + { + output[dst++] = b0; + if (--count <= 0) + break; + output[dst++] = b1; + } + while (--count > 0); + continue; + } + else + { + output[dst++] = b; + } + ++y; + } + return true; + } + + void CopyPlanes (int dst) + { + for (int y = 0; y < m_info.iHeight; ++y) + { + int b0 = m_planes[0][y]; + int b1 = m_planes[1][y]; + int b2 = m_planes[2][y]; + int b3 = m_planes[3][y]; + for (int j = 0; j < 8; j += 2) + { + byte px = (byte)((((b0 << j) & 0x80) >> 3) + | (((b1 << j) & 0x80) >> 2) + | (((b2 << j) & 0x80) >> 1) + | (((b3 << j) & 0x80) )); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + m_output[dst+j/2] = px; + } + dst += m_output_stride; + } + } + + BitmapPalette ReadPalette (ArcView file, int offset) + { + const int count = 16; + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + byte b = file.View.ReadByte (offset++); + byte r = file.View.ReadByte (offset++); + byte g = file.View.ReadByte (offset++); + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + } +} diff --git a/Legacy/Discovery/ImageAN1.cs b/Legacy/Discovery/ImageAN1.cs new file mode 100644 index 00000000..ff6673c7 --- /dev/null +++ b/Legacy/Discovery/ImageAN1.cs @@ -0,0 +1,80 @@ +//! \file ImageAN1.cs +//! \date 2023 Oct 05 +//! \brief Discovery animation resource (PC-98). +// +// Copyright (C) 2023 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.IO; +using System.Windows.Media; + +namespace GameRes.Formats.Discovery +{ + //[Export(typeof(ImageFormat))] + public class An1Format : Pr1Format + { + public override string Tag => "AN1"; + public override string Description => "Discovery animation resource"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".AN1")) + return null; + return base.ReadMetaData (file); + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new AnReader (file, (PrMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("An1Format.Write not implemented"); + } + } + + internal class AnReader : PrReader + { + public AnReader (IBinaryStream file, PrMetaData info) : base (file, info) + { + } + + public new ImageData Unpack () + { + UnpackPlanes(); + int frame_count = m_planes[0].ToUInt16 (2); + int frame_width = 0x20; + int frame_height = frame_count * 0x20; + int output_stride = frame_width >> 1; + var output = new byte[output_stride * frame_height]; + int src = frame_count * 0x16 + 6; + m_plane_size = (output_stride >> 2) * frame_height; + FlattenPlanes (src, output); + Info.Width = (uint)frame_width; + Info.Height = (uint)frame_height; + return ImageData.Create (Info, PixelFormats.Indexed4, m_palette, output, output_stride); + } + } +} diff --git a/Legacy/Discovery/ImagePR1.cs b/Legacy/Discovery/ImagePR1.cs new file mode 100644 index 00000000..027689a2 --- /dev/null +++ b/Legacy/Discovery/ImagePR1.cs @@ -0,0 +1,343 @@ +//! \file ImagePR1.cs +//! \date 2023 Oct 04 +//! \brief Discovery image format (PC-98). +// +// Copyright (C) 2023 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.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Discovery +{ + internal class PrMetaData : ImageMetaData + { + public byte Flags; + public byte Mask; + + public bool IsLeftToRight => (Flags & 1) != 0; + } + + [Export(typeof(ImageFormat))] + public class Pr1Format : ImageFormat + { + public override string Tag => "PR1"; + public override string Description => "Discovery image format"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasAnyOfExtensions (".PR1", ".AN1")) + return null; + var header = file.ReadHeader (12); + return new PrMetaData { + Width = (uint)header.ToUInt16 (8) << 3, + Height = header.ToUInt16 (0xA), + OffsetX = header.ToUInt16 (2), + OffsetY = header.ToUInt16 (4), + Flags = header[0], + Mask = header[1], + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new PrReader (file, (PrMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Pr1Format.Write not implemented"); + } + } + + internal class PrReader + { + IBinaryStream m_input; + PrMetaData m_info; + + Action IncrementDest; + Func IsDone; + + public PrMetaData Info => m_info; + + public PrReader (IBinaryStream file, PrMetaData info) + { + m_input = file; + m_info = info; + if (m_info.IsLeftToRight) + { + IncrementDest = IncLeftToRight; + IsDone = () => m_dst >= m_plane_size; + } + else + { + IncrementDest = IncTopToBottom; + IsDone = () => m_x >= m_stride; + } + } + + protected BitmapPalette m_palette; + protected int m_stride; + protected int m_plane_size; + protected byte[][] m_planes; + int m_dst; + int m_x; + + protected void UnpackPlanes () + { + const int buffer_slice = 0x410; + m_input.Position = 0xC; + m_palette = ReadPalette(); + m_stride = m_info.iWidth >> 3; + m_plane_size = m_stride * m_info.iHeight; + m_planes = new byte[][] { + new byte[m_plane_size], new byte[m_plane_size], new byte[m_plane_size], new byte[m_plane_size], + }; + var buffer = new byte[buffer_slice * 4]; + var buf_count = new byte[4]; + var offsets = new int[] { 0, buffer_slice, buffer_slice*2, buffer_slice*3 }; + m_dst = 0; + m_x = 0; + while (!IsDone()) + { + int ctl = m_input.ReadByte(); + if (-1 == ctl) + break; + int count = (ctl & 0x1F) + 1; + bool bit = (ctl & 0x20) != 0; + ctl >>= 6; + if (!bit) + { + if (ctl != 0) + { + int src_pos = ctl; + int src_count2 = 1 << (ctl - 1); + int pos = offsets[ctl]; + int count2 = src_count2; + do + { + byte p0 = m_input.ReadUInt8(); + byte p1 = m_input.ReadUInt8(); + byte p2 = m_input.ReadUInt8(); + byte p3 = m_input.ReadUInt8(); + PutPixels (p0, p1, p2, p3); + buffer[pos++] = p0; + buffer[pos++] = p1; + buffer[pos++] = p2; + buffer[pos++] = p3; + } + while (--count > 0 && --count2 > 0); + while (count > 0) + { + int si = offsets[src_pos]; + for (int i = 0; i < src_count2; ++i) + { + byte p0 = buffer[si++]; + byte p1 = buffer[si++]; + byte p2 = buffer[si++]; + byte p3 = buffer[si++]; + PutPixels (p0, p1, p2, p3); + if (--count <= 0) + break; + } + } + offsets[src_pos] += src_count2 * 4; + buf_count[src_pos] += (byte)src_count2; + if (buf_count[src_pos] == 0) + offsets[src_pos] = src_pos * buffer_slice; + } + else + { + while (count --> 0) + { + byte p0 = m_input.ReadUInt8(); + byte p1 = m_input.ReadUInt8(); + byte p2 = m_input.ReadUInt8(); + byte p3 = m_input.ReadUInt8(); + PutPixels (p0, p1, p2, p3); + int pos = offsets[0]; + buffer[pos++] = p0; + buffer[pos++] = p1; + buffer[pos++] = p2; + buffer[pos++] = p3; + offsets[0] += 4; + buf_count[0]++; + if (0 == buf_count[0]) + offsets[0] = 0; + } + } + } + else if (ctl != 0) + { + int count2 = 1 << (ctl - 1); + int off_diff = count2 << 2; + int off_mask = off_diff - 1; + int off = m_input.ReadUInt8() << 2;; + int base_pos = ctl * buffer_slice; + off += base_pos; + int src = off; + while (count > 0) + { + off = src; + for (int i = 0; i < count2; ++i) + { + byte p0 = buffer[off]; + byte p1 = buffer[off+1]; + byte p2 = buffer[off+2]; + byte p3 = buffer[off+3]; + PutPixels (p0, p1, p2, p3); + off += 4; + int pos = off - base_pos; + if ((pos & off_mask) == 0) + off -= off_diff; + if (--count <= 0) + break; + } + } + } + else + { + while (count --> 0) + { + int off = m_input.ReadUInt8() << 2; + byte p0 = buffer[off]; + byte p1 = buffer[off+1]; + byte p2 = buffer[off+2]; + byte p3 = buffer[off+3]; + PutPixels (p0, p1, p2, p3); + } + } + } + } + + public ImageData Unpack () + { + UnpackPlanes(); + int output_stride = m_info.iWidth >> 1; + var output = new byte[output_stride * m_info.iHeight]; + FlattenPlanes (0, output); + return ImageData.Create (m_info, PixelFormats.Indexed4, m_palette, output, output_stride); + } + + void PutPixels (byte p0, byte p1, byte p2, byte p3) + { + if (0xFF == m_info.Mask || true) // we don't do overlaying here, just single image decoding + { + m_planes[0][m_dst] = p0; + m_planes[1][m_dst] = p1; + m_planes[2][m_dst] = p2; + m_planes[3][m_dst] = p3; + } + else + { + byte v = m_info.Mask; + byte mask = p0; + if ((v & 1) != 0) + mask = (byte)~mask; + if ((v & 2) != 0) + mask |= (byte)~p1; + else + mask |= p1; + if ((v & 4) != 0) + mask |= (byte)~p2; + else + mask |= p2; + if ((v & 8) != 0) + mask |= (byte)~p3; + else + mask |= p3; + p0 &= mask; + p1 &= mask; + p2 &= mask; + p3 &= mask; + mask = (byte)~mask; + m_planes[0][m_dst] &= mask; + m_planes[0][m_dst] |= p0; + m_planes[1][m_dst] &= mask; + m_planes[1][m_dst] |= p1; + m_planes[2][m_dst] &= mask; + m_planes[2][m_dst] |= p2; + m_planes[3][m_dst] &= mask; + m_planes[3][m_dst] |= p3; + } + IncrementDest(); + } + + void IncLeftToRight () + { + ++m_dst; + ++m_x; + if (m_x > m_info.iWidth) + m_x = 0; + } + + void IncTopToBottom () + { + m_dst += m_stride; + if (m_dst >= m_plane_size) + m_dst = ++m_x; + } + + internal void FlattenPlanes (int src, byte[] output) + { + int m_dst = 0; + for (; src < m_plane_size; ++src) + { + int b0 = m_planes[0][src]; + int b1 = m_planes[1][src]; + int b2 = m_planes[2][src]; + int b3 = m_planes[3][src]; + for (int j = 0; j < 8; j += 2) + { + byte px = (byte)((((b0 << j) & 0x80) >> 3) + | (((b1 << j) & 0x80) >> 2) + | (((b2 << j) & 0x80) >> 1) + | (((b3 << j) & 0x80) )); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + output[m_dst++] = px; + } + } + } + + BitmapPalette ReadPalette () + { + const int count = 16; + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + byte g = m_input.ReadUInt8(); + byte r = m_input.ReadUInt8(); + byte b = m_input.ReadUInt8(); + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + } +} diff --git a/Legacy/Harvest/ArcDAT.cs b/Legacy/Harvest/ArcDAT.cs new file mode 100644 index 00000000..a9908fd5 --- /dev/null +++ b/Legacy/Harvest/ArcDAT.cs @@ -0,0 +1,65 @@ +//! \file ArcDAT.cs +//! \date 2023 Sep 26 +//! \brief MyHarvest resource archive. +// +// Copyright (C) 2023 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; + +namespace GameRes.Formats.MyHarvest +{ + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag => "DAT/UNA"; + public override string Description => "MyHarvest resource archive"; + public override uint Signature => 0x414E55; // 'UNA' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (4, "001\0")) + return null; + int count = file.View.ReadInt32 (8); + if (!IsSaneCount (count)) + return null; + uint index = 0x20; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index, 0x20); + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index+0x20); + entry.Size = file.View.ReadUInt32 (index+0x24); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index += 0x30; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/Harvest/AudioBGM.cs b/Legacy/Harvest/AudioBGM.cs new file mode 100644 index 00000000..f3221782 --- /dev/null +++ b/Legacy/Harvest/AudioBGM.cs @@ -0,0 +1,56 @@ +//! \file AudioBGM.cs +//! \date 2023 Sep 26 +//! \brief MyHarvest audio format. +// +// Copyright (C) 2023 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; + +namespace GameRes.Formats.MyHarvest +{ + [Export(typeof(AudioFormat))] + public class BgmAudio : AudioFormat + { + public override string Tag => "BGM/HARVEST"; + public override string Description => "MyHarvest audio resource"; + public override uint Signature => 0x304D4742; // 'BMG0' + public override bool CanWrite => false; + + public override SoundInput TryOpen (IBinaryStream file) + { + var header = file.ReadHeader (0x1C); + if (!header.AsciiEqual (0x14, "dar\0")) + return null; + var format = new WaveFormat { + FormatTag = header.ToUInt16 (4), + Channels = header.ToUInt16 (6), + SamplesPerSecond = header.ToUInt32 (8), + AverageBytesPerSecond = header.ToUInt32 (0xC), + BlockAlign = header.ToUInt16 (0x10), + BitsPerSample = header.ToUInt16 (0x12), + }; + uint pcm_size = header.ToUInt32 (0x18); + var region = new StreamRegion (file.AsStream, 0x1C, pcm_size); + return new RawPcmInput (region, format); + } + } +} diff --git a/Legacy/Harvest/AudioSED.cs b/Legacy/Harvest/AudioSED.cs new file mode 100644 index 00000000..5543d225 --- /dev/null +++ b/Legacy/Harvest/AudioSED.cs @@ -0,0 +1,61 @@ +//! \file AudioSED.cs +//! \date 2023 Sep 26 +//! \brief MyHarvest audio format. +// +// Copyright (C) 2023 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; + +namespace GameRes.Formats.MyHarvest +{ + [Export(typeof(AudioFormat))] + public class SedAudio : AudioFormat + { + public override string Tag => "SED/HARVEST"; + public override string Description => "MyHarvest audio resource"; + public override uint Signature => 0x14553; // 'SE' + public override bool CanWrite => false; + + public SedAudio () + { + Signatures = new[] { 0x14553u, 0u }; + } + + public override SoundInput TryOpen (IBinaryStream file) + { + var header = file.ReadHeader (0x18); + if (!header.AsciiEqual (0, "SE") || !header.AsciiEqual (0x12, "da")) + return null; + var format = new WaveFormat { + FormatTag = header.ToUInt16 (2), + Channels = header.ToUInt16 (4), + SamplesPerSecond = header.ToUInt32 (6), + AverageBytesPerSecond = header.ToUInt32 (0xA), + BlockAlign = header.ToUInt16 (0xE), + BitsPerSample = header.ToUInt16 (0x10), + }; + uint pcm_size = header.ToUInt32 (0x14); + var region = new StreamRegion (file.AsStream, 0x18, pcm_size); + return new RawPcmInput (region, format); + } + } +} diff --git a/Legacy/Harvest/ImageUNH.cs b/Legacy/Harvest/ImageUNH.cs new file mode 100644 index 00000000..993adcbf --- /dev/null +++ b/Legacy/Harvest/ImageUNH.cs @@ -0,0 +1,96 @@ +//! \file ImageUNH.cs +//! \date 2023 Sep 26 +//! \brief MyHarvest image format. +// +// Copyright (C) 2023 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.IO; +using System.Windows.Media; + +// [021206][MyHarvest] Idol Mahjong Final Romance 4 + +namespace GameRes.Formats.MyHarvest +{ + [Export(typeof(ImageFormat))] + public class UnhFormat : ImageFormat + { + public override string Tag => "UNH"; + public override string Description => "MyHarvest image format"; + public override uint Signature => 0x30484E55; // 'UNH0' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x18); + if (header.ToInt32 (4) != 1) + return null; + return new ImageMetaData { + Width = header.ToUInt32 (0x10), + Height = header.ToUInt32 (0x14), + BPP = 16, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 0x44; + var pixels = new ushort[info.iWidth * info.iHeight]; + var frame = new ushort[0x1000]; + int frame_pos = 0; + int dst = 0; + byte mask = 0; + int ctl = 0; + while (dst < pixels.Length) + { + mask <<= 1; + if (0 == mask) + { + ctl = file.ReadByte(); + if (-1 == ctl) + break; + mask = 1; + } + ushort word = file.ReadUInt16(); + if ((ctl & mask) == 0) + { + pixels[dst++] = frame[frame_pos++ & 0xFFF] = word; + } + else + { + int offset = word >> 4; + int count = (word & 0xF) + 2; + while (count --> 0) + { + ushort u = frame[offset++ & 0xFFF]; + pixels[dst++] = frame[frame_pos++ & 0xFFF] = u; + } + } + } + return ImageData.Create (info, PixelFormats.Bgr565, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("UnhFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Jam/ImageHTF.cs b/Legacy/Jam/ImageHTF.cs new file mode 100644 index 00000000..3355d9fa --- /dev/null +++ b/Legacy/Jam/ImageHTF.cs @@ -0,0 +1,69 @@ +//! \file ImageHTF.cs +//! \date 2023 Oct 07 +//! \brief Huffman-compressed bitmap. +// +// Copyright (C) 2023 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 GameRes.Compression; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Jam +{ + [Export(typeof(ImageFormat))] + public class HtfFormat : ImageFormat + { + public override string Tag => "HTF"; + public override string Description => "Huffman-compressed bitmap"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".HTF")) + return null; + int unpacked_size = file.ReadInt32(); + if (unpacked_size <= 0 || unpacked_size > 0x1000000) + return null; + using (var huff = new HuffmanStream (file.AsStream, true)) + using (var input = new BinaryStream (huff, file.Name)) + { + return Bmp.ReadMetaData (input); + } + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 4; + using (var input = new HuffmanStream (file.AsStream, true)) + { + var decoder = new BmpBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + return new ImageData (decoder.Frames[0], info); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("HtfFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index ce3dac7f..eb4da4e8 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -78,6 +78,9 @@ + + + @@ -92,9 +95,15 @@ + + + + + + @@ -118,6 +127,7 @@ + @@ -138,9 +148,14 @@ + + + + + @@ -209,8 +224,11 @@ + + + @@ -251,7 +269,7 @@ - + diff --git a/Legacy/Mina/ArcPAK.cs b/Legacy/Mina/ArcPAK.cs new file mode 100644 index 00000000..bc7a74cd --- /dev/null +++ b/Legacy/Mina/ArcPAK.cs @@ -0,0 +1,299 @@ +//! \file ArcPAK.cs +//! \date 2023 Oct 09 +//! \brief Mina resource archive. +// +// Copyright (C) 2023 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; +using System.Windows.Media; + +// [010223][Mina] Storia ~Ouma no Mori no Himegimi-tachi~ + +namespace GameRes.Formats.Mina +{ + [Export(typeof(ArchiveFormat))] + public class BmpPakOpener : ArchiveFormat + { + public override string Tag => "PAK/MINA/BMP"; + public override string Description => "Mina bitmap archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.HasExtension (".PAK")) + return null; + int pos; + for (pos = 0; pos < 0x10; ++pos) + { + if (0 == file.View.ReadByte (pos)) + break; + } + if (pos >= 0x10 || pos <= 4 || !file.View.AsciiEqual (pos-4, ".BMP")) + return null; + using (var input = file.CreateStream()) + { + var dir = new List(); + while (input.PeekByte() != -1) + { + var name = input.ReadCString(); + if (name.Length > 0x10) + return null; + var entry = Create (name); + entry.Offset = input.Position; + input.Seek (5, SeekOrigin.Current); + uint size = input.ReadUInt32(); + entry.Size = size + 9; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + input.Seek (size, SeekOrigin.Current); + } + return new ArcFile (file, this, dir); + } + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + return new BitmapDecoder (input); + } + } + + [Export(typeof(ArchiveFormat))] + public class WavPakOpener : ArchiveFormat + { + public override string Tag => "PAK/MINA/WAV"; + public override string Description => "Mina audio archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.HasExtension (".PAK")) + return null; + int pos; + for (pos = 4; pos < 0x14; ++pos) + { + if (0 == file.View.ReadByte (pos)) + break; + } + if (pos >= 0x14 || pos <= 8 || !file.View.AsciiEqual (pos-4, ".WAV")) + return null; + using (var input = file.CreateStream()) + { + var dir = new List(); + while (input.PeekByte() != -1) + { + uint data_size = input.ReadUInt32(); + var name = input.ReadCString(); + if (name.Length > 0x10) + return null; + var entry = Create (name); + entry.Offset = input.Position; + uint fmt_size = input.ReadUInt32(); + if (fmt_size < 0x10) + return null; + entry.Size = data_size + fmt_size + 4; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + input.Seek (data_size + fmt_size, SeekOrigin.Current); + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + uint fmt_size = arc.File.View.ReadUInt32 (entry.Offset); + uint pcm_size = entry.Size - 4 - fmt_size; + using (var mem = new MemoryStream ((int)fmt_size)) + { + using (var buffer = new BinaryWriter (mem, Encoding.ASCII, true)) + { + buffer.Write (AudioFormat.Wav.Signature); + buffer.Write (entry.Size+0x10); + buffer.Write (0x45564157); // 'WAVE' + buffer.Write (0x20746d66); // 'fmt ' + buffer.Write (fmt_size); + var fmt = arc.File.View.ReadBytes (entry.Offset+4, fmt_size); + buffer.Write (fmt, 0, fmt.Length); + buffer.Write (0x61746164); // 'data' + buffer.Write (pcm_size); + } + var header = mem.ToArray(); + var data = arc.File.CreateStream (entry.Offset+4+fmt_size, pcm_size); + return new PrefixStream (header, data); + } + } + } + + [Export(typeof(ArchiveFormat))] + public class ScriptPakOpener : ArchiveFormat + { + public override string Tag => "PAK/MINA/SPT"; + public override string Description => "Mina scripts archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public ScriptPakOpener () + { + ContainedFormats = new[] { "SCR" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "SCRIPT.PAK")) + return null; + using (var input = file.CreateStream()) + { + var dir = new List(); + while (input.PeekByte() != -1) + { + var name = input.ReadCString(); + if (name.Length > 0x10) + return null; + var entry = Create (name); + entry.Size = input.ReadUInt32(); + entry.Offset = input.Position; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + input.Seek (entry.Size, SeekOrigin.Current); + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var data = arc.File.View.ReadBytes (entry.Offset, entry.Size); + var mem = new MemoryStream (data.Length); + int pos = 0; + while (pos < data.Length) + { + int len = data[pos]+1; + int num = data.ToUInt16 (1); + pos += 3; + for (int j = 0; j < len; ++j) + data[pos+j] = Binary.RotByteR (data[pos+j], 4); + mem.Write (data, pos, len); + mem.WriteByte (0xD); + mem.WriteByte (0xA); + pos += len; + } + mem.Position = 0; + return mem; + } + } + + internal class BmpMetaData : ImageMetaData + { + public byte Flags; + public bool IsCompressed => (Flags & 1) != 0; + } + + internal class BitmapDecoder : IImageDecoder + { + IBinaryStream m_input; + BmpMetaData m_info; + ImageData m_image; + + public Stream Source => m_input.AsStream; + public ImageFormat SourceFormat => null; + public ImageMetaData Info => m_info; + public ImageData Image => m_image ?? (m_image = Unpack()); + + public BitmapDecoder (IBinaryStream input) + { + m_input = input; + m_info = new BmpMetaData { + Width = input.ReadUInt16(), + Height = input.ReadUInt16(), + Flags = input.ReadUInt8(), + }; + m_info.BPP = m_info.IsCompressed ? 32 : 24; + } + + ImageData Unpack () + { + m_input.Position = 9; + if (m_info.IsCompressed) + { + return RleUnpack(); + } + else + { + int bitmap_size = m_info.iWidth * 3 * m_info.iHeight; + var pixels = m_input.ReadBytes (bitmap_size); + return ImageData.Create (m_info, PixelFormats.Rgb24, null, pixels); + } + } + + ImageData RleUnpack () + { + int stride = m_info.iWidth * 4; + var output = new byte[stride * m_info.iHeight]; + byte alpha = 0; + int count = 0; + int dst = 0; + while (dst < output.Length) + { + if (--count <= 0) + { + alpha = m_input.ReadUInt8(); + count = m_input.ReadUInt8(); + } + if (alpha != 0) + { + output[dst+2] = m_input.ReadUInt8(); + output[dst+1] = m_input.ReadUInt8(); + output[dst ] = m_input.ReadUInt8(); + output[dst+3] = alpha; + } + dst += 4; + } + return ImageData.Create (m_info, PixelFormats.Bgra32, null, output, stride); + } + + #region IDisposable members + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_input.Dispose(); + m_disposed = true; + } + } + #endregion + } +} diff --git a/Legacy/Nekotaro/ArcNSC.cs b/Legacy/Nekotaro/ArcNSC.cs index 1eebc879..deeb89ef 100644 --- a/Legacy/Nekotaro/ArcNSC.cs +++ b/Legacy/Nekotaro/ArcNSC.cs @@ -27,6 +27,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +// [991231][Jam] Kakuyuugou Shoujo Ripple-chan // [000331][Jam] Zetsumetsu King // [000630][STONE HEADS] Sei Cosplay Gakuen ~Game Bunkou~ diff --git a/Legacy/Nekotaro/ImageGCmp.cs b/Legacy/Nekotaro/ImageGCmp.cs index f825a7c7..bf4b7f93 100644 --- a/Legacy/Nekotaro/ImageGCmp.cs +++ b/Legacy/Nekotaro/ImageGCmp.cs @@ -24,8 +24,10 @@ // using System; +using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Text.RegularExpressions; using System.Windows.Media; using System.Windows.Media.Imaging; using GameRes.Utility; @@ -44,6 +46,11 @@ namespace GameRes.Formats.Nekotaro public override string Description { get { return "Nekotaro Game System image format"; } } public override uint Signature { get { return 0x706D4347; } } // 'GCmp' + public GCmpFormat () + { + Extensions = new[] { "GCMP", "AIG" }; + } + public override ImageMetaData ReadMetaData (IBinaryStream file) { var header = file.ReadHeader (0x10); @@ -98,6 +105,8 @@ namespace GameRes.Formats.Nekotaro Stride = (info.iWidth + 7) / 8; } + static BitmapPalette LastUsedPalette = null; + public ImageData Unpack () { m_input.Position = 0x10; @@ -106,6 +115,8 @@ namespace GameRes.Formats.Nekotaro pixels = Unpack24bpp(); else pixels = Unpack8bpp(); + if (8 == Info.BPP) + Palette = LastUsedPalette ?? (LastUsedPalette = RetrievePalette() ?? DefaultPalette); return ImageData.CreateFlipped (Info, Format, Palette, pixels, Stride); } @@ -190,14 +201,11 @@ namespace GameRes.Formats.Nekotaro byte[] Unpack8bpp () { if (8 == Info.BPP) - { Format = PixelFormats.Indexed8; - Palette = DefaultPalette; - } else Format = PixelFormats.BlackWhite; int pixel_count = Info.iHeight * Stride; - if (m_info.IsCompressed) + if (!m_info.IsCompressed) return m_input.ReadBytes (pixel_count); var output = new byte[pixel_count]; @@ -272,268 +280,125 @@ namespace GameRes.Formats.Nekotaro return output; } - static readonly BitmapPalette DefaultPalette = new BitmapPalette ( - /* - new Color[] { - Color.FromRgb (0x00, 0x00, 0x00), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0x22, 0x22, 0x22), - Color.FromRgb (0x44, 0x44, 0x44), - Color.FromRgb (0x55, 0x55, 0x55), - Color.FromRgb (0x66, 0x66, 0x66), - Color.FromRgb (0x77, 0x77, 0x77), - Color.FromRgb (0x88, 0x88, 0x88), - Color.FromRgb (0x99, 0x99, 0x99), - Color.FromRgb (0xAA, 0xAA, 0xAA), - Color.FromRgb (0xBB, 0xBB, 0xBB), - Color.FromRgb (0xCC, 0xCC, 0xCC), - Color.FromRgb (0xDD, 0xDD, 0xDD), - Color.FromRgb (0xEE, 0xEE, 0xEE), - Color.FromRgb (0x00, 0xFF, 0x00), - Color.FromRgb (0x1C, 0x09, 0x05), - Color.FromRgb (0x2F, 0x0A, 0x05), - Color.FromRgb (0x4E, 0x04, 0x02), - Color.FromRgb (0x41, 0x0C, 0x05), - Color.FromRgb (0x29, 0x15, 0x36), - Color.FromRgb (0x24, 0x22, 0x21), - Color.FromRgb (0x6C, 0x07, 0x0D), - Color.FromRgb (0x1F, 0x2D, 0x36), - Color.FromRgb (0x4B, 0x21, 0x18), - Color.FromRgb (0x5D, 0x1B, 0x0D), - Color.FromRgb (0x8B, 0x00, 0x36), - Color.FromRgb (0x8E, 0x06, 0x16), - Color.FromRgb (0x7E, 0x11, 0x0F), - Color.FromRgb (0x09, 0x44, 0x64), - Color.FromRgb (0x48, 0x2C, 0x4B), - Color.FromRgb (0x38, 0x37, 0x3A), - Color.FromRgb (0x3A, 0x24, 0x88), - Color.FromRgb (0x74, 0x23, 0x12), - Color.FromRgb (0x0D, 0x53, 0x29), - Color.FromRgb (0x22, 0x34, 0x86), - Color.FromRgb (0xB1, 0x03, 0x2A), - Color.FromRgb (0x4B, 0x37, 0x28), - Color.FromRgb (0x64, 0x30, 0x28), - Color.FromRgb (0x32, 0x4A, 0x2D), - Color.FromRgb (0x9B, 0x17, 0x20), - Color.FromRgb (0xB0, 0x10, 0x10), - Color.FromRgb (0x3D, 0x19, 0xCC), - Color.FromRgb (0x1B, 0x38, 0xB2), - Color.FromRgb (0x97, 0x25, 0x13), - Color.FromRgb (0x30, 0x4C, 0x5E), - Color.FromRgb (0x77, 0x38, 0x22), - Color.FromRgb (0xD3, 0x0B, 0x1F), - Color.FromRgb (0x01, 0x69, 0x65), - Color.FromRgb (0x5F, 0x46, 0x33), - Color.FromRgb (0x4B, 0x4D, 0x4F), - Color.FromRgb (0xB6, 0x1B, 0x34), - Color.FromRgb (0x0A, 0x74, 0x34), - Color.FromRgb (0xBB, 0x26, 0x11), - Color.FromRgb (0xED, 0x0B, 0x26), - Color.FromRgb (0x2F, 0x52, 0x97), - Color.FromRgb (0x49, 0x20, 0xFB), - Color.FromRgb (0x89, 0x44, 0x15), - Color.FromRgb (0x67, 0x46, 0x65), - Color.FromRgb (0x06, 0x76, 0x72), - Color.FromRgb (0x93, 0x3F, 0x2D), - Color.FromRgb (0x3F, 0x65, 0x49), - Color.FromRgb (0x6D, 0x52, 0x3B), - Color.FromRgb (0x88, 0x4C, 0x38), - Color.FromRgb (0xE5, 0x26, 0x17), - Color.FromRgb (0xA6, 0x47, 0x1D), - Color.FromRgb (0x43, 0x68, 0x7D), - Color.FromRgb (0x23, 0x50, 0xE8), - Color.FromRgb (0xE3, 0x24, 0x43), - Color.FromRgb (0x94, 0x56, 0x1C), - Color.FromRgb (0x60, 0x63, 0x64), - Color.FromRgb (0xBC, 0x3E, 0x49), - Color.FromRgb (0x06, 0x9C, 0x45), - Color.FromRgb (0xC4, 0x44, 0x24), - Color.FromRgb (0xB1, 0x55, 0x2B), - Color.FromRgb (0x8D, 0x60, 0x53), - Color.FromRgb (0x63, 0x46, 0xFB), - Color.FromRgb (0x7B, 0x6C, 0x61), - Color.FromRgb (0x91, 0x57, 0x97), - Color.FromRgb (0xAA, 0x5A, 0x4C), - Color.FromRgb (0x49, 0x7E, 0xA0), - Color.FromRgb (0xF8, 0x3C, 0x29), - Color.FromRgb (0xA9, 0x67, 0x20), - Color.FromRgb (0xC9, 0x56, 0x36), - Color.FromRgb (0xA2, 0x6A, 0x3E), - Color.FromRgb (0xBF, 0x56, 0x6C), - Color.FromRgb (0x77, 0x7A, 0x7B), - Color.FromRgb (0x5D, 0x79, 0xD2), - Color.FromRgb (0xCC, 0x62, 0x44), - Color.FromRgb (0xA3, 0x75, 0x63), - Color.FromRgb (0xDE, 0x60, 0x31), - Color.FromRgb (0xB5, 0x79, 0x23), - Color.FromRgb (0x45, 0x80, 0xF5), - Color.FromRgb (0xFD, 0x56, 0x29), - Color.FromRgb (0xEE, 0x52, 0x64), - Color.FromRgb (0x8C, 0x83, 0x6E), - Color.FromRgb (0xCD, 0x70, 0x2A), - Color.FromRgb (0xC4, 0x6E, 0x53), - Color.FromRgb (0x86, 0x87, 0x87), - Color.FromRgb (0x5E, 0x95, 0xAC), - Color.FromRgb (0x7D, 0x6C, 0xFD), - Color.FromRgb (0x36, 0xC5, 0x22), - Color.FromRgb (0xAC, 0x6F, 0xB2), - Color.FromRgb (0xD9, 0x6E, 0x4E), - Color.FromRgb (0xC1, 0x84, 0x2D), - Color.FromRgb (0xDB, 0x6C, 0x72), - Color.FromRgb (0xEB, 0x6F, 0x42), - Color.FromRgb (0x9F, 0x8B, 0x81), - Color.FromRgb (0x92, 0x94, 0x93), - Color.FromRgb (0x76, 0x90, 0xDB), - Color.FromRgb (0x85, 0x9A, 0x99), - Color.FromRgb (0xE0, 0x79, 0x58), - Color.FromRgb (0xBE, 0x87, 0x6D), - Color.FromRgb (0xD5, 0x7E, 0x62), - Color.FromRgb (0x5B, 0xA7, 0xDF), - Color.FromRgb (0xCC, 0x91, 0x2A), - Color.FromRgb (0xF5, 0x6F, 0x76), - Color.FromRgb (0x7B, 0xA7, 0xA7), - Color.FromRgb (0xF1, 0x7C, 0x54), - Color.FromRgb (0xA1, 0x9C, 0x87), - Color.FromRgb (0xE5, 0x81, 0x61), - Color.FromRgb (0xF2, 0x8A, 0x47), - Color.FromRgb (0xEE, 0x88, 0x67), - Color.FromRgb (0xA1, 0xA3, 0xA3), - Color.FromRgb (0x8A, 0xA0, 0xE5), - Color.FromRgb (0xC4, 0x9A, 0x7F), - Color.FromRgb (0xD9, 0x9F, 0x36), - Color.FromRgb (0x95, 0xAC, 0xAA), - Color.FromRgb (0xEC, 0x88, 0x8B), - Color.FromRgb (0xAE, 0xA7, 0x92), - Color.FromRgb (0xE8, 0x90, 0x70), - Color.FromRgb (0xF5, 0x8F, 0x6F), - Color.FromRgb (0xD5, 0x8B, 0xDC), - Color.FromRgb (0x6A, 0xC2, 0xF7), - Color.FromRgb (0xEE, 0x9A, 0x7A), - Color.FromRgb (0xF7, 0x98, 0x74), - Color.FromRgb (0x8D, 0xBA, 0xDB), - Color.FromRgb (0xBA, 0xB1, 0x9C), - Color.FromRgb (0xB2, 0xB3, 0xB1), - Color.FromRgb (0xD2, 0xA8, 0x9B), - Color.FromRgb (0xA6, 0xBA, 0xBD), - Color.FromRgb (0xEC, 0xB4, 0x3A), - Color.FromRgb (0xFC, 0x98, 0x9F), - Color.FromRgb (0xF7, 0xA1, 0x80), - Color.FromRgb (0xED, 0xA7, 0x85), - Color.FromRgb (0xFA, 0xA9, 0x83), - Color.FromRgb (0xDD, 0xB3, 0xAF), - Color.FromRgb (0xFA, 0xA6, 0xA7), - Color.FromRgb (0xC8, 0xC0, 0xAD), - Color.FromRgb (0xFA, 0xB0, 0x8F), - Color.FromRgb (0x89, 0xD9, 0xFC), - Color.FromRgb (0xA9, 0xCF, 0xE8), - Color.FromRgb (0xBB, 0xCC, 0xCB), - Color.FromRgb (0xFB, 0xB2, 0xB2), - Color.FromRgb (0xFB, 0xB9, 0x97), - Color.FromRgb (0xE2, 0xC2, 0xAF), - Color.FromRgb (0xFC, 0xCA, 0x40), - Color.FromRgb (0xFA, 0xBF, 0x82), - Color.FromRgb (0xC9, 0xCA, 0xC9), - Color.FromRgb (0xF8, 0xAC, 0xF8), - Color.FromRgb (0xD4, 0xCD, 0xC2), - Color.FromRgb (0xFC, 0xC2, 0x9D), - Color.FromRgb (0xFC, 0xBE, 0xBA), - Color.FromRgb (0xD2, 0xD3, 0xD0), - Color.FromRgb (0xEC, 0xC9, 0xC6), - Color.FromRgb (0xCA, 0xD9, 0xD7), - Color.FromRgb (0xFD, 0xCA, 0xA5), - Color.FromRgb (0xFE, 0xDB, 0x5B), - Color.FromRgb (0xD8, 0xD8, 0xD4), - Color.FromRgb (0xFD, 0xCA, 0xC9), - Color.FromRgb (0xC3, 0xDF, 0xF1), - Color.FromRgb (0xFE, 0xD2, 0xB1), - Color.FromRgb (0xFD, 0xD6, 0xA1), - Color.FromRgb (0xEE, 0xD7, 0xCA), - Color.FromRgb (0xFB, 0xCB, 0xF7), - Color.FromRgb (0xFE, 0xDB, 0xB6), - Color.FromRgb (0xFE, 0xF5, 0x2C), - Color.FromRgb (0xFD, 0xD6, 0xD4), - Color.FromRgb (0xE2, 0xE2, 0xDC), - Color.FromRgb (0xFE, 0xEC, 0x74), - Color.FromRgb (0xFE, 0xE1, 0xBE), - Color.FromRgb (0xED, 0xE5, 0xDC), - Color.FromRgb (0xD9, 0xEC, 0xF8), - Color.FromRgb (0xFB, 0xE3, 0xD4), - Color.FromRgb (0xFD, 0xDD, 0xFA), - Color.FromRgb (0xFE, 0xE7, 0xC6), - Color.FromRgb (0xFE, 0xFA, 0x91), - Color.FromRgb (0xFE, 0xEF, 0xCD), - Color.FromRgb (0xFC, 0xEB, 0xEA), - Color.FromRgb (0xFE, 0xF6, 0xDC), - Color.FromRgb (0xFE, 0xFD, 0xE4), - Color.FromRgb (0x35, 0x29, 0x24), - Color.FromRgb (0x1A, 0x43, 0x25), - Color.FromRgb (0x01, 0x49, 0x96), - Color.FromRgb (0x86, 0x27, 0x16), - Color.FromRgb (0x4D, 0x52, 0x3F), - Color.FromRgb (0xEB, 0x0E, 0x0A), - Color.FromRgb (0x00, 0x6A, 0xCC), - Color.FromRgb (0x80, 0x34, 0xC1), - Color.FromRgb (0xFD, 0x00, 0xFF), - Color.FromRgb (0x08, 0x87, 0xEF), - Color.FromRgb (0x76, 0x70, 0x56), - Color.FromRgb (0xB8, 0x55, 0x3F), - Color.FromRgb (0x35, 0x9F, 0xE1), - Color.FromRgb (0xAA, 0x7E, 0x60), - Color.FromRgb (0x01, 0xFD, 0x00), - Color.FromRgb (0xAB, 0x93, 0x8A), - Color.FromRgb (0xD3, 0x8C, 0x56), - Color.FromRgb (0x77, 0xC0, 0xAC), - Color.FromRgb (0xB9, 0xA6, 0x9E), - Color.FromRgb (0xE6, 0xAB, 0x63), - Color.FromRgb (0x9D, 0xCC, 0xA5), - Color.FromRgb (0xD1, 0xB6, 0x91), - Color.FromRgb (0xA6, 0xD9, 0xCF), - Color.FromRgb (0xEA, 0xC9, 0x9D), - Color.FromRgb (0xDF, 0xE2, 0xBC), - Color.FromRgb (0xFC, 0xE8, 0xA2), - Color.FromRgb (0xF9, 0xF2, 0xDE), - Color.FromRgb (0x23, 0x0D, 0x1A), - Color.FromRgb (0x02, 0x58, 0x1A), - Color.FromRgb (0x66, 0x39, 0x13), - Color.FromRgb (0x36, 0x6D, 0x66), - Color.FromRgb (0x90, 0x5F, 0x2A), - Color.FromRgb (0x51, 0x9E, 0x7E), - Color.FromRgb (0xC0, 0x91, 0x52), - Color.FromRgb (0x7F, 0xC3, 0xAE), - Color.FromRgb (0xE0, 0xBF, 0x78), - Color.FromRgb (0xDC, 0xE8, 0xD4), - Color.FromRgb (0x65, 0x39, 0x12), - Color.FromRgb (0x22, 0x69, 0x49), - Color.FromRgb (0x90, 0x5E, 0x2B), - Color.FromRgb (0x36, 0x87, 0x5F), - Color.FromRgb (0x53, 0x9F, 0x81), - Color.FromRgb (0xBB, 0x85, 0x4C), - Color.FromRgb (0xD9, 0xB9, 0x6F), - Color.FromRgb (0x9A, 0xCE, 0xC2), - Color.FromRgb (0xDF, 0xEF, 0xDE), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), + static void LzssUnpack (IBinaryStream input, byte[] output) + { + int dst = 0; + int mask = 0; + int ctl = 0; + while (dst < output.Length) + { + mask >>= 1; + if (0 == mask) + { + ctl = input.ReadUInt8(); + mask = 0x80; + } + if ((ctl & mask) != 0) + { + int off = input.ReadUInt16(); + int count = (off & 0xF) + 3; + off >>= 4; + int src = dst - off - 1; + Binary.CopyOverlapped (output, src, dst, count); + dst += count; + } + else + { + output[dst++] = input.ReadUInt8(); + } } - */ + } + + BitmapPalette RetrievePalette () + { + // find SYSTEM.LZS file, decompress and read it as text file + // find 'P' line that denotes archive name and entry number + // if entry number is zero, then it's just a file (possibly compressed) + // open referenced file and retrieve palette + try + { + string system_name = "SYSTEM.LZS"; + if (!File.Exists (system_name)) + { + system_name = @"..\SYSTEM.LZS"; + if (!File.Exists (system_name)) + return null; + } + byte[] system_bin; + using (var input = BinaryStream.FromFile (system_name)) + { + int unpacked_size = input.ReadUInt16(); + input.ReadUInt16(); + system_bin = new byte[unpacked_size]; + LzssUnpack (input, system_bin); + } + string line; + using (var mem = new MemoryStream (system_bin)) + using (var text = new StreamReader (mem, Encodings.cp932)) + { + while ((line = text.ReadLine()) != null) + { + if (line.Length > 3 && line.StartsWith ("P:")) + break; + } + if (null == line) + return null; + } + var match = PLineRe.Match (line); + if (!match.Success) + return null; + int id; + if (!Int32.TryParse (match.Groups[2].Value, out id)) + return null; + var arc_name = Path.Combine (Path.GetDirectoryName (system_name), match.Groups[1].Value); + if (0 == id) + { + using (var file = BinaryStream.FromFile (arc_name)) + { + Stream pal_stream; + int unpacked_size = file.ReadUInt16(); + int packed_size = file.ReadUInt16(); + if (packed_size + 4 == file.Length) + { + var pal_data = new byte[unpacked_size]; + LzssUnpack (file, pal_data); + pal_stream = new MemoryStream (pal_data); + } + else + { + file.Position = 0; + pal_stream = file.AsStream; + } + int colors = (int)pal_stream.Length / 3; + using (pal_stream) + return ImageFormat.ReadPalette (pal_stream, colors, PaletteFormat.Rgb); + } + } + else + { + using (var file = new ArcView (arc_name)) + { + var arc = Nsc.Value.TryOpen (file); + if (null == arc) + return null; + var entry = ((List)arc.Dir)[id-1]; + using (var input = arc.OpenEntry (entry)) + return ImageFormat.ReadPalette (input, 0x100, PaletteFormat.Rgb); + } + } + } + catch + { + return null; + } + } + + static readonly Regex PLineRe = new Regex (@"^P:([^,]+),(\d+),(\d+)", RegexOptions.Compiled); + static readonly ResourceInstance Nsc = new ResourceInstance ("NSC"); + + static readonly BitmapPalette DefaultPalette = new BitmapPalette ( // [000317][PIL] Seek -remasters- +#region colors new Color[] { Color.FromRgb (0x00, 0x00, 0x00), Color.FromRgb (0xFF, 0xFF, 0xFF), @@ -792,6 +657,7 @@ namespace GameRes.Formats.Nekotaro Color.FromRgb (0x00, 0x00, 0x00), Color.FromRgb (0x00, 0x00, 0x00), } +#endregion ); bool m_disposed = false; diff --git a/Legacy/Nekotaro/ImageNCG.cs b/Legacy/Nekotaro/ImageNCG.cs new file mode 100644 index 00000000..7b0f5281 --- /dev/null +++ b/Legacy/Nekotaro/ImageNCG.cs @@ -0,0 +1,293 @@ +//! \file ImageNCG.cs +//! \date 2023 Oct 10 +//! \brief Nekotaro Game System image format (PC-98). +// +// Copyright (C) 2023 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.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Nekotaro +{ + [Export(typeof(ImageFormat))] + public class NcgFormat : ImageFormat + { + public override string Tag => "NCG"; + public override string Description => "Nekotaro Game System image format"; + public override uint Signature => 0; + + public NcgFormat () + { + Signatures = new[] { 0xC8500000u, 0u }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (4); + int left = header[0] << 3; + int top = header[1] << 1; + int width = header[2] << 3; + int height = header[3] << 1; + int right = left + width; + int bottom = top + height; + if (right > 640 || bottom > 400 || 0 == width || 0 == height) + return null; + return new ImageMetaData { + Width = (uint)width, + Height = (uint)height, + OffsetX = left, + OffsetY = top, + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new NcgReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("NcgFormat.Write not implemented"); + } + } + + internal class NcgReader + { + IBinaryStream m_input; + ImageMetaData m_info; + + public NcgReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + } + + public ImageData Unpack () + { + m_input.Position = 4; + var palette = ReadPalette(); + int width = m_info.iWidth; + int height = m_info.iHeight; + int output_stride = width; + var pixels = new byte[output_stride * height]; + int quart_width = width / 4; + int half_height = height / 2; + var blockmap = new bool[quart_width * half_height]; + var bits1 = new byte[8]; + var bits2 = new byte[8]; + byte ctl; + int dst, pblk; + do + { + for (int i = 0; i < 8; ++i) + bits1[i] = bits2[i] = 0; + for (int shift = 0; shift < 4; ++shift) + { + byte bit = (byte)(1 << shift); + FillBits (bits1, bit); + FillBits (bits2, bit); + } + for (;;) + { + ctl = m_input.ReadUInt8(); + if (0xFF == ctl || 0x7F == ctl) + break; + int pos = (ctl & 0x3F) << 8 | m_input.ReadUInt8(); + int x = (pos % 80) << 3; + int y = (pos / 80) << 1; + dst = width * y + x; + pblk = x / 4 + quart_width * (y / 2); + switch (ctl >> 6) + { + case 0: + { + int w_count = m_input.ReadUInt8(); + int h_count = m_input.ReadUInt8(); + int gap = quart_width - 2 * w_count; + while (h_count --> 0) + { + for (int i = 0; i < w_count; ++i) + { + for (int j = 0; j < 8; ++j) + { + pixels[dst+width] = bits2[j]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk++] = true; + blockmap[pblk++] = true; + } + pblk += gap; + dst += 2 * width - 8 * w_count; + } + break; + } + case 1: + { + int count = m_input.ReadUInt8(); + while (count --> 0) + { + for (int j = 0; j < 8; ++j) + { + pixels[dst+width] = bits2[j]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk++] = true; + blockmap[pblk++] = true; + } + break; + } + case 2: + { + int count = m_input.ReadUInt8(); + while (count --> 0) + { + for (int j = 0; j < 8; ++j) + { + pixels[dst+width] = bits2[j]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk ] = true; + blockmap[pblk+1] = true; + dst += 2 * width - 8; + pblk += quart_width; + } + break; + } + case 3: + { + for (int j = 0; j < 8; ++j) + { + pixels[dst+width] = bits2[j]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk ] = true; + blockmap[pblk+1] = true; + break; + } + } + } + } + while (ctl != 0xFF); + do + { + for (int i = 0; i < 8; ++i) + bits1[i] = 0; + for (int shift = 0; shift < 4; ++shift) + FillBits (bits1, (byte)(1 << shift)); + for (;;) + { + ctl = m_input.ReadUInt8(); + if (0xFF == ctl || 0xFE == ctl) + break; + int pos = (ctl & 0x7F) << 8 | m_input.ReadUInt8(); + dst = 4 * (pos % 160) + width * 2 * (pos / 160); + pblk = (pos % 160) + quart_width * (pos / 160); + if ((ctl & 0x80) == 0) + { + int count = m_input.ReadUInt8(); + while (count --> 0) + { + for (int j = 0; j < 4; ++j) + { + pixels[dst+width] = bits1[j+4]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk] = true; + pblk += quart_width; + dst += 2 * width - 4; + } + } + else + { + for (int j = 0; j < 4; ++j) + { + pixels[dst+width] = bits1[j+4]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk] = true; + } + } + } + while (ctl != 0xFF); + dst = 0; + pblk = 0; + for (int y = 0; y < half_height; ++y) + { + for (int x = 0; x < quart_width; ++x) + { + if (blockmap[pblk++]) + { + dst += 4; + } + else + { + for (int i = 0; i < 8; ++i) + bits1[i] = 0; + for (int shift = 0; shift < 4; ++shift) + FillBits (bits1, (byte)(1 << shift)); + for (int j = 0; j < 4; ++j) + { + pixels[dst+width] = bits1[j+4]; + pixels[dst++] = bits1[j]; + } + } + } + dst += width; + } + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, output_stride); + } + + void FillBits (byte[] bits, byte bit) + { + sbyte s = m_input.ReadInt8(); + for (int i = 0; i < 8; ++i) + { + if (s < 0) + bits[i] |= bit; + s <<= 1; + } + } + + static readonly string PaletteKey = "NEKOTARO"; + + BitmapPalette ReadPalette () + { + int k = 0; + var colors = new Color[16]; + for (int c = 0; c < 16; ++c) + { + int g = m_input.ReadUInt8(); + int r = m_input.ReadUInt8(); + int b = m_input.ReadUInt8(); + b = (~b - PaletteKey[k++ & 7]) & 0xFF; + r = (~r - PaletteKey[k++ & 7]) & 0xFF; + g = (~g - PaletteKey[k++ & 7]) & 0xFF; + colors[c] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + } +} diff --git a/Legacy/Ponytail/ArcBND.cs b/Legacy/Ponytail/ArcBND.cs new file mode 100644 index 00000000..d92ba2b3 --- /dev/null +++ b/Legacy/Ponytail/ArcBND.cs @@ -0,0 +1,125 @@ +//! \file ArcBND.cs +//! \date 2023 Sep 28 +//! \brief Ponytail Adventure System resource archive. +// +// Copyright (C) 2023 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; + +// [951115][Ponytail Soft] Masuzume Yume + +namespace GameRes.Formats.Ponytail +{ + [Export(typeof(ArchiveFormat))] + public class BndOpener : ArchiveFormat + { + public override string Tag => "BND/NMI"; + public override string Description => "Ponytail Soft resource archive"; + public override uint Signature => 0x646E6942; // 'Bind' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (4, " ver.0")) + return null; + int count = file.View.ReadInt16 (0xD); + if (!IsSaneCount (count)) + return null; + uint index_offset = file.View.ReadUInt32 (0xF); + if (index_offset >= file.MaxOffset) + return null; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_offset, 8).Trim(); + var ext = file.View.ReadString (index_offset+8, 3); + name = name + '.' + ext; + var entry = Create (name); + entry.Size = file.View.ReadUInt32 (index_offset+0x0C); + entry.Offset = file.View.ReadUInt32 (index_offset+0x14); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 0x18; + } + foreach (PackedEntry entry in dir.Where (e => e.Name.EndsWith ("Z") && e.Type != "image")) + { + if (file.View.AsciiEqual (entry.Offset, "lz1_")) + { + entry.IsPacked = true; + char last_chr =(char)file.View.ReadByte (entry.Offset+4); + entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset+5); + string name = entry.Name.Remove (entry.Name.Length-1); + entry.Name = name + char.ToUpperInvariant (last_chr); + } + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = (PackedEntry)entry; + if (!pent.IsPacked) + return base.OpenEntry (arc, entry); + var output = new byte[pent.UnpackedSize]; + using (var input = arc.File.CreateStream (pent.Offset+9, pent.Size-9)) + Lz1Unpack (input, output); + return new BinMemoryStream (output, pent.Name); + } + + internal static void Lz1Unpack (IBinaryStream input, byte[] output) + { + byte mask = 0; + int ctl = 0; + int dst = 0; + while (dst < output.Length) + { + mask <<= 1; + if (0 == mask) + { + ctl = input.ReadUInt8(); + if (ctl < 0) + break; + mask = 1; + } + if ((ctl & mask) != 0) + { + output[dst++] = input.ReadUInt8(); + } + else + { + int code = input.ReadUInt16(); + int offset = (code >> 5) + 1; + int count = Math.Min (3 + (code & 0x1F), output.Length - dst); + Binary.CopyOverlapped (output, dst - offset, dst, count); + dst += count; + } + } + } + } +} diff --git a/Legacy/Ponytail/ImageTCZ.cs b/Legacy/Ponytail/ImageTCZ.cs new file mode 100644 index 00000000..fc79a3ff --- /dev/null +++ b/Legacy/Ponytail/ImageTCZ.cs @@ -0,0 +1,212 @@ +//! \file ImageTCZ.cs +//! \date 2023 Sep 28 +//! \brief Ponytail NMI 2.5 image format (PC-98). +// +// Copyright (C) 2023 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 GameRes.Utility; +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// Graphics driver version 0.3 95/10/30 +// by Y.Nakamura / NARIMI.A + +namespace GameRes.Formats.Ponytail +{ + [Export(typeof(ImageFormat))] + public class TczFormat : ImageFormat + { + public override string Tag => "TCZ"; + public override string Description => "Ponytail Soft NMI image format"; + public override uint Signature => 0x20494D4E; // 'NMI ' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + if (!header.AsciiEqual (4, "2.5\0")) + return null; + var info = new ImageMetaData { + Width = header.ToUInt16 (0xC), + Height = header.ToUInt16 (0xE), + OffsetX = header.ToInt16 (0x8), + OffsetY = header.ToInt16 (0xA), + BPP = 4, + }; + if (info.Height > TczReader.MaxHeight) + return null; + return info; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new TczReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("TczFormat.Write not implemented"); + } + } + + internal class TczReader : TszReader + { + internal const int MaxHeight = 0x190; + const int BufferSize = MaxHeight * 0x10; + + public TczReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + m_stride = m_info.iWidth >> 1; + } + + byte[] m_buffer; + + static short[] s_offTable0 = { -4, -3, -2, -1, 0, 1, 2, 3 }; + static short[] s_offTable1 = { -16, -8, -6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 6, 8, 10, 16 }; + + public new ImageData Unpack () + { + m_input.Position = 0x10; + var palette = ReadPalette(); + var output = new byte[m_stride * m_info.iHeight]; + FillBuffers(); + ResetBitReader(); + int dst = 2; + int output_pos = 0; + int x = 0; + while (x < m_stride) + { + int y = 0; + while (y < m_info.iHeight) + { + if (GetNextBit()) // @1@ + { + int src = dst; + if (!GetNextBit()) // @2@ + { + src += m_lineOffsets1[1]; + int off = GetBits (4); + src += s_offTable1[off]; + } + else if (!GetNextBit()) // @3@ + { + src += GetBits (2) - 4; + } + else + { + if (!GetNextBit()) // @4@ + { + src += m_lineOffsets1[2]; + } + else if (!GetNextBit()) // @5@ + { + src += m_lineOffsets1[4]; + } + else + { + src += m_lineOffsets1[8]; + } + int off = GetBits (3); + src += s_offTable0[off]; + } + src &= 0xFFFF; + int count = GetBitLength() + 1; + Binary.CopyOverlapped (m_buffer, src, dst, count); + y += count; + dst += count; + } + else + { + int bx = m_buffer[dst-2]; + bx = (bx << 8 | bx) & 0xF00F; + if (GetNextBit()) // @8@ + { + int ax = GetBits (4); + bx = (bx & 0xFF) | ax << 12; + } + if (GetNextBit()) // @9@ + { + int ax = GetBits (4); + bx = (bx & 0xFF00) | ax; + } + bx = (bx & 0xFF) | (bx >> 8); + m_buffer[dst++] = (byte)bx; + ++y; + } + } + ++x; + if ((x & 3) == 0) + { + CopyScanline (m_lineOffsets0[(x - 1) & 0xC], output, output_pos); + output_pos += 4; + } + int z = x & 0xF; + dst = m_lineOffsets0[z]; + if (z != 0) + { + m_lineOffsets1[z] -= BufferSize; + } + else + { + for (int i = 1; i < 16; ++i) + { + m_lineOffsets1[i] += BufferSize; + } + } + } + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, m_stride); + } + + ushort[] m_lineOffsets0 = new ushort[16]; + ushort[] m_lineOffsets1 = new ushort[16]; + + void FillBuffers () + { + m_buffer = new byte[BufferSize + MaxHeight]; + m_buffer[0] = m_buffer[1] = 3; + ushort p = 2; + for (int i = 0; i < 16; ++i) + { + m_lineOffsets1[i] = (ushort)(((16 - i) & 0xF) * MaxHeight); + m_lineOffsets0[i] = p; + p += MaxHeight; + } + } + + void CopyScanline (int src, byte[] output, int dst) + { + for (int i = 0; i < m_info.iHeight; ++i) + { + output[dst ] = m_buffer[src + i ]; + output[dst+1] = m_buffer[src + i + MaxHeight ]; + output[dst+2] = m_buffer[src + i + MaxHeight*2]; + output[dst+3] = m_buffer[src + i + MaxHeight*3]; + dst += m_stride; + } + } + } +} diff --git a/Legacy/Ponytail/ImageTSZ.cs b/Legacy/Ponytail/ImageTSZ.cs new file mode 100644 index 00000000..57cea061 --- /dev/null +++ b/Legacy/Ponytail/ImageTSZ.cs @@ -0,0 +1,330 @@ +//! \file ImageTSZ.cs +//! \date 2023 Sep 27 +//! \brief Ponytail NMI 2.05 image format (PC-98). +// +// Copyright (C) 2023 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.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [930413][Ponytail Soft] Yougen Doumu + +namespace GameRes.Formats.Ponytail +{ + [Export(typeof(ImageFormat))] + public class TszFormat : ImageFormat + { + public override string Tag => "TSZ"; + public override string Description => "Ponytail Soft NMI image format"; + public override uint Signature => 0x20494D4E; // 'NMI ' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + if (!header.AsciiEqual (4, "2.05")) + return null; + return new ImageMetaData { + Width = (uint)header.ToUInt16 (0xC) << 2, + Height = header.ToUInt16 (0xE), + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new TszReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("TszFormat.Write not implemented"); + } + } + + internal class TszReader + { + protected IBinaryStream m_input; + protected ImageMetaData m_info; + protected int m_stride; + + protected TszReader () { } + + public TszReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + m_stride = m_info.iWidth >> 1; + } + + private int m_previous_row; + private ushort[] m_linebuffer; + private int m_dst; + + public ImageData Unpack () + { + m_input.Position = 0x10; + var palette = ReadPalette(); + var output = new byte[m_stride * m_info.iHeight]; + m_linebuffer = new ushort[m_info.iHeight * 2]; + m_previous_row = m_info.iHeight; + int width = m_info.iWidth >> 2; + int dst_x = 0; + int x = 0; + ResetBitReader(); + while (x < width) + { + int y = 0; + m_dst = 0; + if ((x & 1) != 0) + m_dst += m_info.iHeight; + while (y < m_info.iHeight) + { + int ctl = 0; + while (GetNextBit()) + { + ++ctl; + } + int count; // bx + switch (ctl) + { + case 0: count = CopyMethod0(); break; + case 1: count = CopyMethod1(); break; + case 2: + m_linebuffer[m_dst] = m_input.ReadUInt16(); + count = 1; + break; + case 3: count = CopyMethod3(); break; + case 4: count = CopyMethod4(); break; + default: throw new InvalidFormatException(); + } + m_dst += count; + y += count; + } + if ((x & 1) != 0) + { + CopyScanline (output, dst_x); + dst_x += 4; + } + m_previous_row = -m_previous_row; + ++x; + } + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, m_stride); + } + + void CopyScanline (byte[] output, int dst) + { + for (int i = 0; i < m_info.iHeight; ++i) + { + ushort px1 = m_linebuffer[i]; + ushort px2 = m_linebuffer[i + m_info.iHeight]; + // these bytes contain 8 pixels that are being put into 4 planes + int b0 = (px1 << 4) & 0xF0 | (px2 ) & 0x0F; + int b1 = (px1 ) & 0xF0 | (px2 >> 4) & 0x0F; + int b2 = (px1 >> 4) & 0xF0 | (px2 >> 8) & 0x0F; + int b3 = (px1 >> 8) & 0xF0 | (px2 >> 12) & 0x0F; + // repack pixels into flat surface, 2 pixels per byte + for (int j = 0; j < 8; j += 2) + { + byte px = (byte)((((b0 << j) & 0x80) >> 3) + | (((b1 << j) & 0x80) >> 2) + | (((b2 << j) & 0x80) >> 1) + | (((b3 << j) & 0x80) )); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + output[dst+j/2] = px; + } + dst += m_stride; + } + } + + int CopyMethod0 () + { + int count = GetBitLength(); + int offset = GetBits (4); + offset += m_previous_row - 8; + CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count); + return count; + } + + int CopyMethod1 () + { + int count = GetBitLength(); + int offset = m_input.ReadUInt8(); + offset += m_previous_row - 0x80; + CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count); + return count; + } + + int CopyMethod3 () + { + int count = GetBitLength(); + int offset = GetBits (4); + offset -= 0x10; + CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count); + return count; + } + + int CopyMethod4 () + { + byte al = m_input.ReadUInt8(); + int nibble = al >> 4; + ushort mask1 = s_pattern1[al >> 4]; + ushort mask2 = s_pattern2[al & 0xF]; + ushort pixel = m_linebuffer[m_dst + m_previous_row]; + pixel &= mask1; + for (int i = 0; i < 4; ++i) + { + short carry = (short)(nibble & 1); + nibble >>= 1; + pixel |= (ushort)(-carry & mask2); + pixel = RotU16R (pixel, 1); + } + pixel = RotU16L (pixel, 4); + m_linebuffer[m_dst] = pixel; + return 1; + } + + static readonly ushort[] s_pattern1 = new ushort[] { + 0xFFFF, 0xEEEE, 0xDDDD, 0xCCCC, 0xBBBB, 0xAAAA, 0x9999, 0x8888, + 0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000, + }; + static readonly ushort[] s_pattern2 = new ushort[] { + 0x0000, 0x0001, 0x0010, 0x0011, 0x0100, 0x0101, 0x0110, 0x0111, + 0x1000, 0x1001, 0x1010, 0x1011, 0x1100, 0x1101, 0x1110, 0x1111, + }; + + ushort m_bits; + int m_bit_count; + + protected void ResetBitReader () + { + m_bits = 0; + m_bit_count = 0; + } + + protected int GetBitLength () + { + if (!GetNextBit()) + return 1; + int count = 1; + while (GetNextBit()) + { + ++count; + } + return GetBits (count) | 1 << count; + } + + protected bool GetNextBit () + { + if (--m_bit_count < 0) + { + m_bits = m_input.ReadUInt16(); + m_bit_count = 15; + } + bool bit = (m_bits & 0x8000) != 0; + m_bits <<= 1; + return bit; + } + + static readonly ushort[] s_bit_mask = new ushort[] { + 0, 1, 3, 7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, + }; + + protected int GetBits (int count) + { + m_bit_count -= count; + if (m_bit_count < 0) + { + m_bits = RotU16L (m_bits, count); + int cl = -m_bit_count; + ushort bits = m_input.ReadUInt16(); + bits = RotU16L (bits, cl); + ushort mask = s_bit_mask[cl]; + ushort new_bits = (ushort)(bits & ~mask); + bits &= mask; + bits |= m_bits; + m_bits = new_bits; + m_bit_count = 16 - cl; + return bits; + } + else + { + m_bits = RotU16L (m_bits, count); + ushort mask = s_bit_mask[count]; + int bits = m_bits & mask; + m_bits &= (ushort)~mask; + return bits; + } + } + + protected BitmapPalette ReadPalette () + { + const int count = 16; + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + byte r = m_input.ReadUInt8(); + byte g = m_input.ReadUInt8(); + byte b = m_input.ReadUInt8(); + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + + static internal ushort RotU16L (ushort val, int count) + { + return (ushort)(val << count | val >> (16 - count)); + } + + static internal ushort RotU16R (ushort val, int count) + { + return (ushort)(val >> count | val << (16 - count)); + } + + static internal void CopyOverlapped (ushort[] data, int src, int dst, int count) + { + src <<= 1; + dst <<= 1; + count <<= 1; + if (dst > src) + { + while (count > 0) + { + int preceding = Math.Min (dst - src, count); + Buffer.BlockCopy (data, src, data, dst, preceding); + dst += preceding; + count -= preceding; + } + } + else + { + Buffer.BlockCopy (data, src, data, dst, count); + } + } + } +} diff --git a/Legacy/Properties/AssemblyInfo.cs b/Legacy/Properties/AssemblyInfo.cs index d63b355a..99ace8d5 100644 --- a/Legacy/Properties/AssemblyInfo.cs +++ b/Legacy/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("1.0.10.208")] -[assembly: AssemblyFileVersion ("1.0.10.208")] +[assembly: AssemblyVersion ("1.0.10.210")] +[assembly: AssemblyFileVersion ("1.0.10.210")] diff --git a/Legacy/Sophia/ArcNOR.cs b/Legacy/Sophia/ArcNOR.cs new file mode 100644 index 00000000..003243c3 --- /dev/null +++ b/Legacy/Sophia/ArcNOR.cs @@ -0,0 +1,146 @@ +//! \file ArcNOR.cs +//! \date 2023 Sep 26 +//! \brief Sophia resource archive. +// +// Copyright (C) 2023 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +// [991210][Sophia] Film Noir + +namespace GameRes.Formats.Sophia +{ + internal class NorEntry : PackedEntry + { + public int Method; + } + + [Export(typeof(ArchiveFormat))] + public class NorOpener : ArchiveFormat + { + public override string Tag => "NOR"; + public override string Description => "Sophia resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (0); + if (!file.View.AsciiEqual (4, "NRCOMB01\0") || !IsSaneCount (count)) + return null; + var dir = new List (count); + using (var index = file.CreateStream()) + { + index.Position = 0x10; + for (int i = 0; i < count; ++i) + { + uint offset = index.ReadUInt32(); + uint size = index.ReadUInt32(); + string name = index.ReadCString(); + var entry = Create (name); + entry.Offset = offset; + entry.Size = size; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var nent = (NorEntry)entry; + if (!nent.IsPacked) + { + if (nent.Method == 0x1F4 || nent.Method == 0x67 + || !arc.File.View.AsciiEqual (nent.Offset, "NCMB01")) + return base.OpenEntry (arc, nent); + nent.Method = arc.File.View.ReadInt32 (nent.Offset+0x28); + if (nent.Method == 0x1F4 || nent.Method == 0x67) + { + nent.Size = arc.File.View.ReadUInt32 (nent.Offset+0x24); + nent.Offset += 0x2C; + return base.OpenEntry (arc, nent); + } + nent.IsPacked = true; + nent.UnpackedSize = arc.File.View.ReadUInt32 (nent.Offset+0x24); + nent.Size = arc.File.View.ReadUInt32 (nent.Offset+0x10); + nent.Offset += 0x2C; + } + using (var input = arc.File.CreateStream (nent.Offset, nent.Size)) + { + var output = new byte[nent.UnpackedSize]; + NcmbDecompress (input, output); + return new BinMemoryStream (output, nent.Name); + } + } + + internal static void NcmbDecompress (IBinaryStream input, byte[] output) + { + var dict = new int[0xC00]; + int root = input.ReadInt32(); + int tree_size = input.ReadInt32(); + int unpacked_size = input.ReadInt32(); + int count = root + tree_size - 0xFF; + while (count --> 0) + { + int token = 6 * input.ReadInt32(); + dict[token ] = input.ReadInt32(); + dict[token + 1] = input.ReadInt32(); + } + if (unpacked_size > 0) + { + int cur_byte = 0; + int mask = 0; + for (int dst = 0; dst < unpacked_size; ++dst) + { + int token = root; + do + { + if (0 == mask) + { + cur_byte = input.ReadUInt8(); + mask = 0x80; + } + if ((cur_byte & mask) != 0) + token = dict[6 * token + 1]; + else + token = dict[6 * token]; + mask >>= 1; + } + while (dict[6 * token] != -1); + output[dst] = (byte)token; + } + } + } + } + + [Export(typeof(ResourceAlias))] + [ExportMetadata("Extension", "M")] + [ExportMetadata("Target", "MP3")] + [ExportMetadata("Type", "audio")] + public class MFormat : ResourceAlias { } +} diff --git a/Legacy/SquadraD/ArcPLA.cs b/Legacy/SquadraD/ArcPLA.cs new file mode 100644 index 00000000..c9ac5e84 --- /dev/null +++ b/Legacy/SquadraD/ArcPLA.cs @@ -0,0 +1,114 @@ +//! \file ArcPLA.cs +//! \date 2023 Sep 26 +//! \brief Squadra D audio archive. +// +// Copyright (C) 2023 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; + +namespace GameRes.Formats.SquadraD +{ + internal class PlaEntry : PackedEntry + { + public int Id; + public int n1; + public uint SampleRate; + public int Channels; + public byte n2; + public byte n3; + public int[] Data; + } + + [Export(typeof(ArchiveFormat))] + public class PlaOpener : ArchiveFormat + { + public override string Tag => "PLA"; + public override string Description => "Squadra D audio archive"; + public override uint Signature => 0x2E616C50; // 'Pla.' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + uint arc_size = file.View.ReadUInt32 (4); + if (arc_size != file.MaxOffset || file.View.ReadUInt32 (0x10) != 2) + return null; + uint check = (arc_size & 0xD5555555u) << 1 | arc_size & 0xAAAAAAAAu; + if (check != file.View.ReadUInt32 (8)) + return null; + int count = file.View.ReadUInt16 (0xE); + if (!IsSaneCount (count)) + return null; + + var dir = new List (count); + using (var index = file.CreateStream()) + { + index.Position = 0x14; + for (int i = 0; i < count; ++i) + { + var entry = new PlaEntry { + Id = index.ReadInt32() + }; + entry.Name = entry.Id.ToString ("D5"); + dir.Add (entry); + } + foreach (PlaEntry entry in dir) + { + entry.n1 = index.ReadInt32(); + entry.SampleRate = index.ReadUInt32(); + entry.Channels = index.ReadInt32(); + entry.n2 = index.ReadUInt8(); + entry.n3 = index.ReadUInt8(); + index.ReadInt16(); + } + foreach (PlaEntry entry in dir) + { + entry.Offset = index.ReadUInt32(); + } + foreach (PlaEntry entry in dir) + { + int n = entry.Channels * 2; + entry.Data = new int[n]; + for (int j = 0; j < n; ++j) + entry.Data[j] = index.ReadInt32(); + } + } + long last_offset = file.MaxOffset; + for (int i = dir.Count - 1; i >= 0; --i) + { + dir[i].Size = (uint)(last_offset - dir[i].Offset); + last_offset = dir[i].Offset; + } + return new ArcFile (file, this, dir); + } + + /* + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + return base.OpenEntry (arc, entry); + } + */ + } +} diff --git a/Legacy/SquadraD/ArcSDA.cs b/Legacy/SquadraD/ArcSDA.cs new file mode 100644 index 00000000..dabf18bc --- /dev/null +++ b/Legacy/SquadraD/ArcSDA.cs @@ -0,0 +1,121 @@ +//! \file ArcSDA.cs +//! \date 2023 Sep 26 +//! \brief Squadra D resource archive format. +// +// Copyright (C) 2023 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; + +namespace GameRes.Formats.SquadraD +{ + [Export(typeof(ArchiveFormat))] + public class SdaOpener : ArchiveFormat + { + public override string Tag => "SDA/SD"; + public override string Description => "Squadra D resource archive"; + public override uint Signature => 0x4153; // 'SA' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public SdaOpener () + { + Signatures = new[] { 0x4153u, 0xCC004153u, 0u }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "SA\0")) + return null; + int data_offset = file.View.ReadInt32 (4); + if (data_offset <= 8 || data_offset >= file.MaxOffset) + return null; + int count = (data_offset - 8) / 0x14; + if (!IsSaneCount (count)) + return null; + string arc_name = Path.GetFileNameWithoutExtension (file.Name); + bool is_cg = arc_name == "g"; + uint index = 8; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index, 0x10).Trim();; + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index+0xC) + data_offset; + entry.Size = file.View.ReadUInt32 (index+0x10); + if (is_cg) + entry.Type = "image"; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index += 0x14; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + using (var input = arc.File.CreateStream (entry.Offset, entry.Size)) + { + var data = LzssDecompress (input); + return new BinMemoryStream (data, entry.Name); + } + } + + internal static byte[] LzssDecompress (IBinaryStream input) + { + int unpacked_size = input.ReadInt32(); + var output = new byte[unpacked_size]; + using (var bits = new LsbBitStream (input.AsStream, true)) + { + var frame = new byte[0x1000]; + int frame_pos = 0xFC0; + int dst = 0; + while (dst < unpacked_size) + { + if (bits.GetNextBit() == 0) + { + byte b = (byte)bits.GetBits (8); + output[dst++] = frame[frame_pos++ & 0xFFF] = b; + } + else + { + int count_len = 4; + if (bits.GetNextBit() != 0) + count_len = 6; + int offset = bits.GetBits (12); + int count = bits.GetBits (count_len); + count = Math.Min (count + 3, unpacked_size - dst); + while (count --> 0) + { + byte b = frame[offset++ & 0xFFF]; + output[dst++] = frame[frame_pos++ & 0xFFF] = b; + } + } + } + return output; + } + } + } +} diff --git a/Legacy/WestGate/ArcUCA.cs b/Legacy/WestGate/ArcUCA.cs index 7b9f66cd..821870a2 100644 --- a/Legacy/WestGate/ArcUCA.cs +++ b/Legacy/WestGate/ArcUCA.cs @@ -74,13 +74,15 @@ namespace GameRes.Formats.WestGate uint next_offset = file.View.ReadUInt32 (index_offset+0xC); if (next_offset < data_offset) return null; + string last_name = null; var invalid_chars = Path.GetInvalidFileNameChars(); var dir = new List (count); for (int i = 0; i < count; ++i) { var name = file.View.ReadString (index_offset, 0xC); - if (string.IsNullOrWhiteSpace (name) || name.IndexOfAny (invalid_chars) != -1) + if (last_name == name || string.IsNullOrWhiteSpace (name) || name.IndexOfAny (invalid_chars) != -1) return null; + last_name = name; index_offset += 0x10; var entry = new Entry { Name = name, Type = entry_type }; entry.Offset = next_offset;