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