diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 8112c600..02efc896 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -136,6 +136,11 @@ + + + + WidgetBELLDATA.xaml + @@ -1163,6 +1168,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/ArcFormats/Cyberworks/ArcDAT.cs b/ArcFormats/Cyberworks/ArcDAT.cs index 2f8f689e..56c59d2c 100644 --- a/ArcFormats/Cyberworks/ArcDAT.cs +++ b/ArcFormats/Cyberworks/ArcDAT.cs @@ -319,10 +319,17 @@ namespace GameRes.Formats.Cyberworks } else if (scheme != null && ('a' == type || 'd' == type) && input.Length > 21) { - int id = input.ReadByte(); - if (id == scheme.Value2) + if (!scheme.UseDataDecoder) { - return new AImageReader (input, scheme, type); + int id = input.ReadByte(); + if (id == scheme.Value2) + { + return new AImageReader(input, scheme, type); + } + } + else + { + return new DataImageDecoder(input,type); } } input.Position = 0; @@ -377,6 +384,7 @@ namespace GameRes.Formats.Cyberworks public byte Value3; public byte[] HeaderOrder; public bool Flipped; + public bool UseDataDecoder; public AImageScheme () { diff --git a/ArcFormats/Cyberworks/ArcDATA.cs b/ArcFormats/Cyberworks/ArcDATA.cs new file mode 100644 index 00000000..f45d0d53 --- /dev/null +++ b/ArcFormats/Cyberworks/ArcDATA.cs @@ -0,0 +1,242 @@ +//! \file ArcDATA.cs +//! \date 2025 Apr 14 +//! \brief Tinker Bell resource archive in Resources subdirectory. +// +// Copyright (C) 2016-2017 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.IO; +using System.ComponentModel.Composition; +using System.Collections.Generic; +using GameRes.Compression; +using GameRes.Formats.Strings; +using GameRes.Utility; +using GameRes.Strings; + +namespace GameRes.Formats.Cyberworks +{ + [Serializable] + public class DataScheme + { + public int ExtraHeaderSize; + } + + [Serializable] + public class DataSchemeMap : ResourceScheme + { + public Dictionary KnownSchemes; + } + + public class DataOptions : ResourceOptions + { + public string Scheme; + } + + [Export(typeof(ArchiveFormat))] + public class DataOpener : ArchiveFormat + { + public override string Tag { get { return "DATA/Csystem"; } } + public override string Description { get { return "TinkerBell resource archive in Resources subdirectory"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + static DataSchemeMap DefaultScheme = new DataSchemeMap { KnownSchemes = new Dictionary() }; + + static public Dictionary KnownSchemes { get { return DefaultScheme.KnownSchemes; } } + + public override ResourceScheme Scheme + { + get { return DefaultScheme; } + set { DefaultScheme = (DataSchemeMap)value; } + } + + public override ArcFile TryOpen(ArcView file) + { + var arc_name = Path.GetFileName(file.Name); + var dir_name = VFS.GetDirectoryName(file.Name); + if ("Data00.dat".Equals(arc_name, StringComparison.OrdinalIgnoreCase)) + return null; + if (!int.TryParse(arc_name.Substring(4, arc_name.IndexOf('.') - 4), out int arc_index)) + return null; + var scheme = QueryScheme(arc_name); + var dir = ScanDir(VFS.CombinePath(dir_name, "Data00.dat"), arc_index, scheme); + if (null == dir || 0 == dir.Count) + return null; + + return new ArcFile(file, this, dir); + + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (0 == entry.Size) + throw new FileSizeException(garStrings.MsgFileIsEmpty); //in some cases,the size of entry maybe 0,like game Mujina(vndb.org/v48764). + + Stream input = arc.File.CreateStream(entry.Offset, entry.Size); + var pent = entry as PackedEntry; + if (null != pent && pent.IsPacked) + { + input = new LzssStream(input); + } + return input; + } + + public override IImageDecoder OpenImage(ArcFile arc, Entry entry) + { + + if(entry.Size < 0x1D) + return base.OpenImage(arc, entry); + var input = arc.OpenBinaryEntry(entry); + try + { + var reader = DecryptImage(input); + var data_img_decoder = reader as DataImageDecoder; + if (null != data_img_decoder + && data_img_decoder.HasBaseImage) + { + data_img_decoder.ReadBaseImage(arc); + } + return reader; + } + catch + { + input.Dispose(); + throw; + } + + } + + protected virtual IImageDecoder DecryptImage(IBinaryStream input) + { + var type = input.ReadByte(); + switch (type) + { + case 0x61: + case 0x64: + { + return new DataImageDecoder(input,type); + } + case 0x62: + case 0x63: + { + var img_size = Binary.BigEndian(input.ReadUInt32()); + var start_pos = input.Length - img_size; + input = BinaryStream.FromStream(new StreamRegion(input.AsStream, start_pos, img_size),input.Name); + break; + } + } + input.Position = 0; + return new ImageFormatDecoder(input); + } + + List ScanDir(string toc_arc_name, int arc_index, DataScheme scheme) + { + if(!VFS.FileExists(toc_arc_name)) + return null; + + var dir = new List(); + var toc_offset = 0; + + using (var toc_file = VFS.OpenView(toc_arc_name)) + { + var data_file_count = toc_file.View.ReadInt32(toc_offset); + toc_offset += 4; + + if(arc_index >= data_file_count) + return null; + + for (var i = 0; i < data_file_count; ++i) + { + var data_entry_count = toc_file.View.ReadInt32(toc_offset); + toc_offset += 4; + + for (var j = 0; j < data_entry_count; ++j) + { + if (i == arc_index) + { + var entry = new PackedEntry { Name = string.Format("{0:D4}", j) }; + entry.UnpackedSize = toc_file.View.ReadUInt32(toc_offset); + entry.Size = toc_file.View.ReadUInt32(toc_offset + 4); + entry.Offset = toc_file.View.ReadUInt32(toc_offset + 8); + entry.IsPacked = (0 != entry.UnpackedSize); + entry.Type = "image"; + dir.Add(entry); + + } + toc_offset += (0xC + scheme.ExtraHeaderSize); + } + + var unknow_chunkA_count = toc_file.View.ReadInt32(toc_offset); + toc_offset += 4; + for (var j = 0; j < unknow_chunkA_count; ++j) + { + toc_offset += 0xC; + var chunkA_extra_count = toc_file.View.ReadInt32(toc_offset); + toc_offset += 4; + toc_offset += ((chunkA_extra_count-1) * 4); + toc_offset += (chunkA_extra_count * 0xC); + } + + var unknow_chunkB_count = toc_file.View.ReadInt32(toc_offset); + toc_offset += 4; + if (0 != unknow_chunkB_count) + { + toc_offset += 4; + var chunkB_extra_count = toc_file.View.ReadInt32(toc_offset); + toc_offset += 4; + toc_offset += (chunkB_extra_count*8); + } + + } + + } + + return dir; + } + + DataScheme QueryScheme(string arc_name) + { + var title = FormatCatalog.Instance.LookupGame(arc_name, @"..\*.exe"); + DataScheme scheme = new DataScheme(); + + if (!string.IsNullOrEmpty(title) && KnownSchemes.TryGetValue(title, out scheme)) + return scheme; + var options = Query(arcStrings.ArcEncryptedNotice); + if (null != options) + KnownSchemes.TryGetValue(options.Scheme,out scheme); + return scheme; + + } + + public override object GetAccessWidget() + { + return new GUI.WidgetBELLDATA(); + } + + public override ResourceOptions GetDefaultOptions() + { + return new DataOptions { Scheme = Properties.Settings.Default.BELLDATATitle }; + } + + } +} diff --git a/ArcFormats/Cyberworks/ImageDATA.cs b/ArcFormats/Cyberworks/ImageDATA.cs new file mode 100644 index 00000000..df8a44c4 --- /dev/null +++ b/ArcFormats/Cyberworks/ImageDATA.cs @@ -0,0 +1,319 @@ +//! \file ImageDATA.cs +//! \date 2025 Apr 16 +//! \brief Tinker Bell image format when have Resources subdirectory +// +// Copyright (C) 2016-2022 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.IO; +using System.Collections.Generic; +using System.Windows.Media; + +namespace GameRes.Formats.Cyberworks +{ + internal sealed class DataImageDecoder : IImageDecoder + { + public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } } + + public ImageFormat SourceFormat { get { return null; } } + + public ImageMetaData Info { get { return m_info; } } + + public ImageData Image + { + get + { + if (null == m_image) + CreateImageData(); + return m_image; + } + } + + public bool HasBaseImage { get; private set; } + + ImageMetaData m_info = new ImageMetaData(); + IBinaryStream m_input; + byte[] m_output; + ImageData m_image; + int m_type; + int m_flag; + + public DataImageDecoder(IBinaryStream input, int type) + { + m_input = input; + m_type = type; + + m_input.Position = 0x9; + + m_info.Width = m_input.ReadUInt32(); + m_info.Height = m_input.ReadUInt32(); + m_info.BPP = 32; //format : BGRA32 + m_info.OffsetX = 0; + m_info.OffsetY = 0; + + m_input.Position = 0x11; + m_flag = input.ReadByte(); + if (0x64 == type || (0x61 == type && 1 == m_flag)) + HasBaseImage = true; + + } + + void CreateImageData() + { + switch (m_type) + { + case 0x61: + { + if (0 == m_flag) + { + FillPixelsType61Pure(); + } + else if (1 == m_flag) + { + FillPixelsType61HasBase(); + } + else if (0 != (m_flag & 2) && 0 != (m_flag & 4)) + { + FillPixelsType61ExtendedAlpha(); + } + break; + } + case 0x64: + { + FillPixelsType64(); + break; + } + } + + var stride = (m_info.iWidth * m_info.BPP + 7) / 8; + m_image = ImageData.Create(m_info, PixelFormats.Bgra32, null, m_output, stride); + } + + internal void ReadBaseImage(ArcFile arc) + { + if (!HasBaseImage) + return; + + m_input.Position = 1; + + int base_img_index = (int)(m_input.ReadUInt64() % 10000000); + List dir = arc.Dir as List; + if (null == dir) + return; + + using (var base_img_decoder = arc.OpenImage(dir[base_img_index])) + { + var stride = (base_img_decoder.Info.iWidth * base_img_decoder.Info.BPP + 7) / 8; + var base_pixel = new byte[stride * base_img_decoder.Info.Height]; + base_img_decoder.Image.Bitmap.CopyPixels(base_pixel, stride, 0); + + m_info.Width = base_img_decoder.Info.Width; + m_info.Height = base_img_decoder.Info.Height; + m_output = base_pixel; + } + + } + + void FillPixelsType61Pure() + { + m_output = new byte[m_info.Height * m_info.Width * 4]; + + m_input.Position = 0x1D; + var pixel_buffer_size = m_input.ReadInt32(); + + m_input.Position = 0x21; + var pixel_buffer = m_input.ReadBytes(pixel_buffer_size); + + var pixel_offset = 0; + var output_offset = 0; + if (pixel_buffer_size == m_info.Width * m_info.Height) + { + + for (uint y = 0; y < m_info.Height; y++) + { + for (uint x = 0; x < m_info.Width; x++) + { + m_output[output_offset+0] = pixel_buffer[pixel_offset]; //write b channel + m_output[output_offset+1] = pixel_buffer[pixel_offset]; //write g channel + m_output[output_offset+2] = pixel_buffer[pixel_offset]; //write r channel + m_output[output_offset+3] = pixel_buffer[pixel_offset]; //write a channel + pixel_offset++; + output_offset += 4; + } + } + } + else if (pixel_buffer_size >= m_info.Width * m_info.Height * 3) + { + int paddingByte = ((m_info.iWidth * 3 + 3) & ~3) - (m_info.iWidth * 3); + + for (uint y = 0; y < m_info.Height; y++) + { + for (uint x = 0; x < m_info.Width; x++) + { + m_output[output_offset+0] = pixel_buffer[pixel_offset+0]; //write b channel + m_output[output_offset+1] = pixel_buffer[pixel_offset+1]; //write g channel + m_output[output_offset+2] = pixel_buffer[pixel_offset+2]; //write r channel + m_output[output_offset+3] = 0xFF; //write a channel + pixel_offset += 3; + output_offset += 4; + } + pixel_offset += paddingByte; + } + + } + else + { + throw new NotImplementedException(); + } + } + + void FillPixelsType61HasBase() + { + m_input.Position = 0x19; + + var pixel_diff_mask_size = m_input.ReadInt32(); + var pixel_diff_mask_buffer = m_input.ReadBytes(pixel_diff_mask_size); + m_input.Position = pixel_diff_mask_size + 0x1D; + var pixel_delta_size = m_input.ReadInt32(); + var pixel_delta_buffer = m_input.ReadBytes(pixel_delta_size); + + var output_offset = 0; + var delta_offset = 0; + var diff_mask_offset = 0; + var mask_bit_offset = 0; + for (int y = 0; y < m_info.Height; y++) + { + for (int x = 0; x < m_info.Width; x++) + { + if (1 == ((pixel_diff_mask_buffer[diff_mask_offset] >> mask_bit_offset) & 1)) + { + m_output[output_offset+0] = pixel_delta_buffer[delta_offset+0]; //write b channel + m_output[output_offset+1] = pixel_delta_buffer[delta_offset+1]; //write g channel + m_output[output_offset+2] = pixel_delta_buffer[delta_offset+2]; //write r channel + delta_offset += 3; + } + m_output[output_offset+3] = 0xFF; //write a channel + output_offset += 4; + if (7 == mask_bit_offset) + { + mask_bit_offset = 0; + diff_mask_offset++; + } + else + { + mask_bit_offset++; + } + } + } + + } + + void FillPixelsType61ExtendedAlpha() + { + m_output = new byte[m_info.Height * m_info.Width * 4]; + + m_input.Position = 0x15; + var alpha_buffer_size = m_input.ReadInt32(); + var alpha_buffer = m_input.ReadBytes(alpha_buffer_size); + + m_input.Position = alpha_buffer_size + 0x1D; + var bgr_buffer_size = m_input.ReadInt32(); + var bgr_buffer = m_input.ReadBytes(bgr_buffer_size); + + var output_offset = 0; + var pixel_offset = 0; + for (int y = 0; y < m_info.Height; y++) + { + for (int x = 0; x < m_info.Width; x++) + { + m_output[output_offset+0] = bgr_buffer[pixel_offset+0]; //write b channel + m_output[output_offset+1] = bgr_buffer[pixel_offset+1]; //write g channel + m_output[output_offset+2] = bgr_buffer[pixel_offset+2]; //write r channel + m_output[output_offset+3] = alpha_buffer[pixel_offset]; //write a channel + output_offset += 4; + pixel_offset += 3; + } + } + + } + + /// + /// if the image entry type is 0x64, the entry doesn't record the image's width and height. + /// + void FillPixelsType64() + { + m_input.Position = 0x15; + var alpha_delta_size = m_input.ReadInt32(); + var alpha_delta_buffer = m_input.ReadBytes(alpha_delta_size); + var diff_mask_size = m_input.ReadInt32(); + var diff_mask_buffer = m_input.ReadBytes(diff_mask_size); + + m_input.Position = alpha_delta_size + diff_mask_size + 0x1D; + var bgr_delta_size = m_input.ReadInt32(); + var bgr_delta_buffer = m_input.ReadBytes(bgr_delta_size); + + var output_offset = 0; + var alpha_delta_offset = 0; + var bgr_delta_offset = 0; + var diff_mask_offset = 0; + var mask_bit_offset = 0; + for (int y = 0; y < m_info.Height; y++) + { + for (int x = 0; x < m_info.Width; x++) + { + if (1 == ((diff_mask_buffer[diff_mask_offset] >> mask_bit_offset) & 1)) + { + m_output[output_offset+0] = bgr_delta_buffer[bgr_delta_offset+0]; //write b channel + m_output[output_offset+1] = bgr_delta_buffer[bgr_delta_offset+1]; //write g channel + m_output[output_offset+2] = bgr_delta_buffer[bgr_delta_offset+2]; //write r channel + m_output[output_offset+3] = alpha_delta_buffer[alpha_delta_offset]; //write a channel + bgr_delta_offset += 3; + alpha_delta_offset++; + } + output_offset += 4; + if (7 == mask_bit_offset) + { + mask_bit_offset = 0; + diff_mask_offset++; + } + else + { + mask_bit_offset++; + } + } + } + + } + + #region IDisposable Members + bool m_disposed = false; + public void Dispose() + { + if (!m_disposed) + { + m_input.Dispose(); + m_disposed = true; + } + } + #endregion + } +} diff --git a/ArcFormats/Cyberworks/WidgetBELLDATA.xaml b/ArcFormats/Cyberworks/WidgetBELLDATA.xaml new file mode 100644 index 00000000..aefff54e --- /dev/null +++ b/ArcFormats/Cyberworks/WidgetBELLDATA.xaml @@ -0,0 +1,7 @@ + + + diff --git a/ArcFormats/Cyberworks/WidgetBELLDATA.xaml.cs b/ArcFormats/Cyberworks/WidgetBELLDATA.xaml.cs new file mode 100644 index 00000000..f27ae183 --- /dev/null +++ b/ArcFormats/Cyberworks/WidgetBELLDATA.xaml.cs @@ -0,0 +1,22 @@ +using System.Linq; +using System.Windows.Controls; +using GameRes.Formats.Cyberworks; +using GameRes.Formats.Strings; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetBELLDATA.xaml + /// + public partial class WidgetBELLDATA : StackPanel + { + public WidgetBELLDATA() + { + InitializeComponent(); + var keys = new string[] { arcStrings.ArcIgnoreEncryption }; + Title.ItemsSource = keys.Concat (DataOpener.KnownSchemes.Keys.OrderBy (x => x)); + if (-1 == Title.SelectedIndex) + Title.SelectedIndex = 0; + } + } +} diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 4bad779b..2c870f61 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -585,7 +585,20 @@ namespace GameRes.Formats.Properties { this["BELLTitle"] = value; } } - + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string BELLDATATitle + { + get { + return ((string)(this["BELLDATATitle"])); + } + set { + this["BELLDATATitle"] = value; + } + } + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] diff --git a/ArcFormats/Resources/Formats.dat b/ArcFormats/Resources/Formats.dat index f940fa2e..2f3346ce 100644 Binary files a/ArcFormats/Resources/Formats.dat and b/ArcFormats/Resources/Formats.dat differ