From f6d437d2018b36ea0af35873acd0f2caffd2dbee Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 19 Sep 2018 12:17:41 +0400 Subject: [PATCH] implemented PPAC archives, TIM2 and TX images. --- ArcFormats/ArcFormats.csproj | 4 + ArcFormats/DigitalWorks/ArcBIN.cs | 180 ++++++++++++++++++++++++++++ ArcFormats/DigitalWorks/ArcPAC.cs | 99 +++++++++++++++ ArcFormats/DigitalWorks/ImageTM2.cs | 152 +++++++++++++++++++++++ ArcFormats/DigitalWorks/ImageTX.cs | 102 ++++++++++++++++ 5 files changed, 537 insertions(+) create mode 100644 ArcFormats/DigitalWorks/ArcBIN.cs create mode 100644 ArcFormats/DigitalWorks/ArcPAC.cs create mode 100644 ArcFormats/DigitalWorks/ImageTM2.cs create mode 100644 ArcFormats/DigitalWorks/ImageTX.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 1e5c43de..b1bc0905 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -151,6 +151,10 @@ + + + + diff --git a/ArcFormats/DigitalWorks/ArcBIN.cs b/ArcFormats/DigitalWorks/ArcBIN.cs new file mode 100644 index 00000000..b004f9b5 --- /dev/null +++ b/ArcFormats/DigitalWorks/ArcBIN.cs @@ -0,0 +1,180 @@ +//! \file ArcBIN.cs +//! \date 2018 Sep 19 +//! \brief Digital Works resource archive. +// +// Copyright (C) 2018 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; +using System.Linq; +using GameRes.Utility; + +namespace GameRes.Formats.DigitalWorks +{ + [Serializable] + public class IndexEntry + { + public uint Offset; + public uint Size; + public bool IsPacked; + public ushort Id; + + public IndexEntry (uint offset, uint size, bool is_packed, ushort id) + { + Offset = offset; + Size = size; + IsPacked = is_packed; + Id = id; + } + } + + [Serializable] + public class BinScheme + { + public string Extension; + public long Size; + public IList Index; + } + + [Serializable] + public class PacScheme : ResourceScheme + { + public IDictionary> KnownSchemes; + } + + [Export(typeof(ArchiveFormat))] + public class BinOpener : PacOpener + { + public override string Tag { get { return "BIN/PAC"; } } + public override string Description { get { return "Digital Works resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.HasAnyOfExtensions ("bin", "pac")) + return null; + var scheme = FindScheme (file); + if (null == scheme) + return null; + var pac_name = Path.GetFileNameWithoutExtension (file.Name); + var dir = scheme.Index.Select (e => new PackedEntry { + Name = string.Format ("{0}{1:D5}.{2}", pac_name, e.Id, scheme.Extension), + Offset = e.Offset, + Size = e.Size, + } as Entry).ToList(); + dir.ForEach (e => e.Type = FormatCatalog.Instance.GetTypeFromName (e.Name)); + return new ArcFile (file, this, dir); + } + + BinScheme FindScheme (ArcView bin_file) + { + var bin_name = Path.GetFileName (bin_file.Name).ToUpperInvariant(); + foreach (var game in KnownSchemes.Values) + { + BinScheme scheme; + if (game.TryGetValue (bin_name, out scheme) && bin_file.MaxOffset == scheme.Size) + return scheme; + } + if (bin_file.MaxOffset >= uint.MaxValue) + return null; + var bin_dir = VFS.GetDirectoryName (bin_file.Name); + var game_dir = Directory.GetParent (bin_dir).FullName; + var exe_files = VFS.GetFiles (VFS.CombinePath (game_dir, "*.exe")); + if (!exe_files.Any()) + return null; + var last_idx = new byte[12]; + LittleEndian.Pack ((uint)bin_file.MaxOffset, last_idx, 0); + LittleEndian.Pack ((uint)bin_file.MaxOffset, last_idx, 4); + foreach (var exe_entry in exe_files) + { + using (var exe_file = VFS.OpenView (exe_entry)) + { + var exe = new ExeFile (exe_file); + if (!exe.ContainsSection (".data")) + continue; + var data_section = exe.Sections[".data"]; + var idx_pos = exe.FindString (data_section, last_idx, 4); + if (idx_pos > 0) + return ParseIndexTable (exe_file, data_section, idx_pos, bin_name); + } + } + return null; + } + + BinScheme ParseIndexTable (ArcView exe_file, ExeFile.Section data, long pos, string bin_name) + { + uint pac_size = exe_file.View.ReadUInt32 (pos); + long last_offset = pac_size; + var dir = new List(); + for (pos -= 12; pos >= data.Offset && last_offset != 0; pos -= 12) + { + long offset = exe_file.View.ReadUInt32 (pos); + uint size = exe_file.View.ReadUInt32 (pos+4); + ushort is_packed = exe_file.View.ReadUInt16 (pos+8); + ushort id = exe_file.View.ReadUInt16 (pos+10); + if (0 == size || offset + size > last_offset || is_packed != 0 && is_packed != 1) + return null; + var entry = new IndexEntry ((uint)offset, size, is_packed != 0, id); + dir.Add (entry); + last_offset = offset; + } + bin_name = Path.GetFileNameWithoutExtension (bin_name).ToUpperInvariant(); + string ext; + if (!PacExtensionMap.TryGetValue (bin_name, out ext)) + ext = ""; + return new BinScheme { + Extension = ext, + Size = pac_size, + Index = dir, + }; + } + + static readonly Dictionary PacExtensionMap = new Dictionary { + { "ANM", "BIN" }, + { "MOV", "MPG" }, + { "STR", "OGG" }, + { "TAK", "BIN" }, + { "VCE", "OGG" }, + { "VIS", "TMX" }, + { "_SE", "OGG" }, + }; + + PacScheme DefaultScheme = new PacScheme { + KnownSchemes = new Dictionary>() + }; + + public IDictionary> KnownSchemes + { + get { return DefaultScheme.KnownSchemes; } + } + + public override ResourceScheme Scheme + { + get { return DefaultScheme; } + set { DefaultScheme = (PacScheme)value; } + } + } +} diff --git a/ArcFormats/DigitalWorks/ArcPAC.cs b/ArcFormats/DigitalWorks/ArcPAC.cs new file mode 100644 index 00000000..1924cce4 --- /dev/null +++ b/ArcFormats/DigitalWorks/ArcPAC.cs @@ -0,0 +1,99 @@ +//! \file ArcPAC.cs +//! \date 2018 Sep 18 +//! \brief Digital Works resource archive. +// +// Copyright (C) 2018 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 GameRes.Compression; + +namespace GameRes.Formats.DigitalWorks +{ + [Export(typeof(ArchiveFormat))] + public class PacOpener : ArchiveFormat + { + public override string Tag { get { return "PAC/HED"; } } + public override string Description { get { return "Digital Works resource archive"; } } + public override uint Signature { get { return 0x43415050; } } // 'PPAC-PAC' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (4, "-PAC")) + return null; + var hed_name = Path.ChangeExtension (file.Name, "hed"); + if (!VFS.FileExists (hed_name)) + return null; + using (var hed = VFS.OpenView (hed_name)) + { + if (!hed.View.AsciiEqual (0, "PPAC-HED")) + return null; + uint index_offset = 0x10; + const uint data_offset = 0x10; + int count = (int)(hed.MaxOffset - index_offset) / 0x20; + if (!IsSaneCount (count)) + return null; + var dir = new List (count); + while (index_offset+0x20 <= hed.MaxOffset) + { + var name = hed.View.ReadString (index_offset, 0x10); + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = hed.View.ReadUInt32 (index_offset+0x10) + data_offset; + entry.Size = hed.View.ReadUInt32 (index_offset+0x14); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 0x20; + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = entry as PackedEntry; + if (null == pent) + return base.OpenEntry (arc, entry); + if (!pent.IsPacked) + { + if (!arc.File.View.AsciiEqual (entry.Offset, "LZS\0")) + return base.OpenEntry (arc, entry); + pent.IsPacked = true; + pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset+4); + } + var input = arc.File.CreateStream (entry.Offset+8, entry.Size-8); + bool embedded_lzs = (input.Signature & ~0xF0u) == 0x535A4C0F; // 'LZS' + var lzs = new LzssStream (input); + if (embedded_lzs) + { + var header = new byte[8]; + lzs.Read (header, 0, 8); + pent.UnpackedSize = header.ToUInt32 (4); + lzs = new LzssStream (lzs); + } + return lzs; + } + } +} diff --git a/ArcFormats/DigitalWorks/ImageTM2.cs b/ArcFormats/DigitalWorks/ImageTM2.cs new file mode 100644 index 00000000..2c3a6afc --- /dev/null +++ b/ArcFormats/DigitalWorks/ImageTM2.cs @@ -0,0 +1,152 @@ +//! \file ImageTM2.cs +//! \date 2018 Sep 18 +//! \brief PlayStation2 image format. +// +// Copyright (C) 2018 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.DigitalWorks +{ + internal class Tim2MetaData : ImageMetaData + { + public int PaletteSize; + public int HeaderSize; + public int Colors; + } + + [Export(typeof(ImageFormat))] + public class Tim2Format : ImageFormat + { + public override string Tag { get { return "TIM2"; } } + public override string Description { get { return "PlayStation/2 image format"; } } + public override uint Signature { get { return 0x324D4954; } } // 'TIM2' + + public Tim2Format () + { + Extensions = new string[] { "tm2", "ext" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x40); + byte bpp = header[0x23]; + switch (bpp) + { + case 1: bpp = 16; break; + case 2: bpp = 24; break; + case 3: bpp = 32; break; + case 5: bpp = 8; break; + default: return null; + } + return new Tim2MetaData { + Width = header.ToUInt16 (0x24), + Height = header.ToUInt16 (0x26), + BPP = bpp, + PaletteSize = header.ToInt32 (0x14), + HeaderSize = header.ToUInt16 (0x1C), + Colors = header.ToUInt16 (0x1E), + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Tim2Reader (file, (Tim2MetaData)info); + var pixels = reader.Unpack(); + return ImageData.Create (info, reader.Format, reader.Palette, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Tim2Format.Write not implemented"); + } + } + + internal class Tim2Reader + { + IBinaryStream m_input; + Tim2MetaData m_info; + + public PixelFormat Format { get; private set; } + public BitmapPalette Palette { get; private set; } + + public Tim2Reader (IBinaryStream input, Tim2MetaData info) + { + m_input = input; + m_info = info; + switch (info.BPP) + { + case 8: Format = PixelFormats.Indexed8; break; + case 16: Format = PixelFormats.Bgr555; break; + case 24: Format = PixelFormats.Bgr24; break; + case 32: Format = PixelFormats.Bgra32; break; + } + } + + public byte[] Unpack () + { + m_input.Position = 0x10 + m_info.HeaderSize; + int pixel_size = m_info.BPP / 8; + int image_size = (int)m_info.Width * (int)m_info.Height * pixel_size; + var output = m_input.ReadBytes (image_size); + if (pixel_size <= 8 && m_info.Colors > 0) + Palette = ReadPalette (m_info.Colors); + + if (pixel_size >= 3) + { + for (int i = 0; i < image_size; i += pixel_size) + { + byte r = output[i]; + output[i] = output[i+2]; + output[i+2] = r; + } + } + return output; + } + + BitmapPalette ReadPalette (int color_num) + { + var source = ImageFormat.ReadColorMap (m_input.AsStream, color_num, PaletteFormat.RgbA); + var color_map = new Color[color_num]; + + int parts = color_num / 32; + const int blocks = 2; + const int rows = 2; + const int colors = 8; + + int dst = 0; + for (int part = 0; part < parts; part++) + for (int block = 0; block < blocks; block++) + for (int row = 0; row < rows; row++) + { + int src = (part * rows * blocks + row * rows + block) * colors; + Array.Copy (source, src, color_map, dst, colors); + dst += colors; + } + return new BitmapPalette (color_map); + } + } +} diff --git a/ArcFormats/DigitalWorks/ImageTX.cs b/ArcFormats/DigitalWorks/ImageTX.cs new file mode 100644 index 00000000..09b98c0d --- /dev/null +++ b/ArcFormats/DigitalWorks/ImageTX.cs @@ -0,0 +1,102 @@ +//! \file ImageTX.cs +//! \date 2018 Sep 19 +//! \brief Digital Works texture format. +// +// Copyright (C) 2018 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.DigitalWorks +{ + [Export(typeof(ImageFormat))] + public class TxFormat : ImageFormat + { + public override string Tag { get { return "TX"; } } + public override string Description { get { return "Digital Works texture format"; } } + public override uint Signature { get { return 0x00035854; } } // 'TX' + + const int BlockSize = 256; + + public TxFormat () + { + Extensions = new string[] { "tmx", "tx" }; + Signatures = new uint[] { 0x00035854, 0x00025854, 0 }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + if (!header.AsciiEqual ("TX")) + return null; + byte bpp = header[6]; + if (bpp < 1 || bpp > 4) + return null; + uint w_blocks = header.ToUInt16 (2); + uint h_blocks = header.ToUInt16 (4); + return new ImageMetaData { + Width = w_blocks * BlockSize, + Height = h_blocks * BlockSize, + BPP = bpp * 8, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 0x10; + int block_stride = BlockSize * (info.BPP / 8); + int stride = (int)info.Width * (info.BPP / 8); + var pixels = new byte[stride * (int)info.Height]; + int x_blocks = (int)info.Width / BlockSize; + int y_blocks = (int)info.Height / BlockSize; + int dst_row = 0; + for (int y = 0; y < y_blocks; ++y) + { + int dst_col = dst_row; + for (int x = 0; x < x_blocks; ++x) + { + int dst = dst_col; + for (int i = 0; i < BlockSize; ++i) + { + file.Read (pixels, dst, block_stride); + dst += stride; + } + dst_col += block_stride; + } + dst_row += stride * BlockSize; + } + BitmapPalette palette = null; + if (8 == info.BPP) + palette = ReadPalette (file.AsStream, 0x100, PaletteFormat.BgrA); + PixelFormat format = 8 == info.BPP ? PixelFormats.Indexed8 + : 24 == info.BPP ? PixelFormats.Bgr24 : PixelFormats.Bgra32; + return ImageData.Create (info, format, palette, pixels, stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("TxFormat.Write not implemented"); + } + } +}