diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index 97e1b49a..388b38a2 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -161,6 +161,7 @@
+
@@ -201,6 +202,7 @@
+
@@ -218,6 +220,7 @@
+
diff --git a/ArcFormats/Leaf/LeafVideo.cs b/ArcFormats/Leaf/LeafVideo.cs
new file mode 100644
index 00000000..ce4922c8
--- /dev/null
+++ b/ArcFormats/Leaf/LeafVideo.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using GameRes.Utility;
+
+namespace GameRes.Formats.Leaf
+{
+ [Export(typeof(ArchiveFormat))]
+ public class VideoOpener : ArchiveFormat
+ {
+ public override string Tag { get { return "VIDEO/LEAF"; } }
+ public override string Description { get { return "Leaf/Aquaplus Video Container"; } }
+ public override uint Signature { get { return 0; } } // Dynamic verification
+ public override bool IsHierarchic { get { return false; } }
+ public override bool CanWrite { get { return false; } }
+
+ public override ArcFile TryOpen(ArcView file)
+ {
+ // Read the first 16 bytes to check the signature
+ if (file.MaxOffset < 16)
+ return null;
+
+ string ext = null;
+ uint head = file.View.ReadUInt32(0);
+
+ // 1. Check if it is WMV/ASF (Leaf standard)
+ // GUID: 30 26 B2 75 8E 66 CF 11 ...
+ // In Little Endian UInt64: 0x11CF668E75B22630
+ if (head == 0x75B22630)
+ {
+ ulong asfGuid = file.View.ReadUInt64(0);
+ if (asfGuid == 0x11CF668E75B22630)
+ {
+ ext = ".wmv";
+ }
+ }
+ // 2. Check if it is AVI (RIFF ... AVI )
+ else if (head == 0x46464952) // "RIFF"
+ {
+ // Check if the type at offset 8 is "AVI "
+ if (file.View.ReadUInt32(8) == 0x20495641)
+ {
+ ext = ".avi";
+ }
+ }
+ // 3. Check if it is MPEG (Reusing logic, in case an old game uses it)
+ else if (head == 0xBA010000)
+ {
+ ext = ".mpg";
+ }
+
+ if (ext == null)
+ return null;
+
+ // Create the virtual entry with the correct extension
+ var entry = new Entry
+ {
+ Name = Path.GetFileNameWithoutExtension(file.Name) + ext,
+ Type = "video",
+ Offset = 0,
+ Size = (uint)file.MaxOffset
+ };
+
+ return new ArcFile(file, this, new List { entry });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Legacy/Broccoli/BroccoliGrp.cs b/Legacy/Broccoli/BroccoliGrp.cs
new file mode 100644
index 00000000..54bd099f
--- /dev/null
+++ b/Legacy/Broccoli/BroccoliGrp.cs
@@ -0,0 +1,144 @@
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Linq; // Added for list operations if necessary
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using GameRes.Utility;
+
+namespace GameRes.Formats.Broccoli
+{
+ [Export(typeof(ImageFormat))]
+ public class GrpFormat : ImageFormat
+ {
+ public override string Tag { get { return "GRP/BROCCOLI"; } }
+ public override string Description { get { return "Broccoli engine image"; } }
+ public override uint Signature { get { return 0; } }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ // We need to decompress to know the real size
+ using (var data = DecompressData(file))
+ {
+ if (data == null) return null;
+
+ var size = data.Length;
+ uint width = 0, height = 0;
+ int bpp = 32;
+
+ // Same resolution logic as your Python script
+ if (size == 1920000) { width = 800; height = 600; }
+ else if (size == 1228800) { width = 640; height = 480; }
+ else if (size == 1536000) { width = 800; height = 480; }
+ else if (size == 768000) { width = 640; height = 400; }
+ else if (size == 307200) { width = 640; height = 480; bpp = 8; }
+ else if (size == 96000) { width = 200; height = 120; }
+ else
+ {
+ // Fallback: Try to read 4-byte header (Width/Height)
+ if (size > 4)
+ {
+ data.Position = 0;
+ var reader = new BinaryReader(data);
+ ushort w = reader.ReadUInt16();
+ ushort h = reader.ReadUInt16();
+ if (w * h * 4 == size - 4)
+ {
+ width = w;
+ height = h;
+ }
+ }
+ }
+
+ if (width == 0) return null;
+
+ return new ImageMetaData
+ {
+ Width = width,
+ Height = height,
+ BPP = bpp,
+ };
+ }
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ using (var stream = DecompressData(file))
+ {
+ if (stream == null) throw new InvalidFormatException();
+
+ // Skip 4-byte header if fallback logic was used
+ int headerSkip = 0;
+ if (stream.Length != info.Width * info.Height * (info.BPP / 8))
+ {
+ if (stream.Length == (info.Width * info.Height * 4) + 4)
+ headerSkip = 4;
+ }
+
+ stream.Position = headerSkip;
+
+ // Case 1: Isolated Mask (Grayscale)
+ if (info.BPP == 8)
+ {
+ var gray = new byte[info.Width * info.Height];
+ stream.Read(gray, 0, gray.Length);
+ return ImageData.Create(info, PixelFormats.Gray8, null, gray);
+ }
+
+ // Case 2: Colored Image (BGRX)
+ // Due to API limitations, we won't load the mask (_m) here.
+ // GARbro will show the image with a black/solid background.
+ // Use your Python script to combine with the mask later.
+
+ var pixels = new byte[info.Width * info.Height * 4];
+ stream.Read(pixels, 0, pixels.Length);
+
+ // The format is usually Bgr32 (the 4th byte is garbage/padding, not real alpha)
+ return ImageData.Create(info, PixelFormats.Bgr32, null, pixels);
+ }
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException("Use repack.py");
+ }
+
+ private MemoryStream DecompressData(IBinaryStream input)
+ {
+ input.Position = 0;
+ byte[] buffer = input.ReadBytes(2048);
+ int zlibOffset = -1;
+
+ // Search for ZLIB signature (78 9C, etc)
+ for (int i = 0; i < buffer.Length - 1; i++)
+ {
+ if (buffer[i] == 0x78 &&
+ (buffer[i+1] == 0x9C || buffer[i+1] == 0xDA || buffer[i+1] == 0x01))
+ {
+ zlibOffset = i;
+ break;
+ }
+ }
+
+ if (zlibOffset == -1) return null;
+
+ try
+ {
+ // Skip ZLIB header (2 bytes) to use standard DeflateStream
+ input.Position = zlibOffset + 2;
+
+ using (var zStream = new System.IO.Compression.DeflateStream(input.AsStream, System.IO.Compression.CompressionMode.Decompress, true))
+ {
+ var output = new MemoryStream();
+ zStream.CopyTo(output);
+ output.Position = 0;
+ return output;
+ }
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Legacy/Broccoli/BroccoliMpeg.cs b/Legacy/Broccoli/BroccoliMpeg.cs
new file mode 100644
index 00000000..a8aec3ed
--- /dev/null
+++ b/Legacy/Broccoli/BroccoliMpeg.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using GameRes.Utility;
+
+namespace GameRes.Formats.Broccoli
+{
+ [Export(typeof(ArchiveFormat))]
+ public class MpegVideoOpener : ArchiveFormat
+ {
+ public override string Tag { get { return "MPEG/BROCCOLI"; } }
+ public override string Description { get { return "Generic MPEG Video"; } }
+ public override uint Signature { get { return 0xBA010000; } }
+ public override bool IsHierarchic { get { return false; } }
+ public override bool CanWrite { get { return false; } }
+
+ public override ArcFile TryOpen(ArcView file)
+ {
+ if (file.View.ReadUInt32(0) != 0xBA010000)
+ return null;
+
+ var entry = new Entry
+ {
+ Name = Path.GetFileNameWithoutExtension(file.Name) + ".mpg",
+ Type = "video",
+ Offset = 0,
+ // FIX: Added (uint) cast for size conversion
+ Size = (uint)file.MaxOffset
+ };
+
+ return new ArcFile(file, this, new List { entry });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Legacy/Broccoli/BroccoliPak.cs b/Legacy/Broccoli/BroccoliPak.cs
new file mode 100644
index 00000000..c2511e6a
--- /dev/null
+++ b/Legacy/Broccoli/BroccoliPak.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Linq;
+using GameRes.Utility;
+
+namespace GameRes.Formats.Broccoli
+{
+ [Export(typeof(ArchiveFormat))]
+ public class PakOpener : ArchiveFormat
+ {
+ public override string Tag { get { return "PAK/BROCCOLI"; } }
+ public override string Description { get { return "Broccoli"; } }
+ public override uint Signature { get { return 0; } }
+ public override bool IsHierarchic { get { return true; } }
+ public override bool CanWrite { get { return false; } }
+
+ private static readonly byte[] FirstFileSignature = {
+ 0x73, 0x65, 0x5F, 0x63, 0x31, 0x31, 0x2E, 0x77, 0x61, 0x76
+ };
+
+ public override ArcFile TryOpen(ArcView file)
+ {
+ int bufferSize = 2048;
+ if (file.MaxOffset < bufferSize)
+ bufferSize = (int)file.MaxOffset;
+
+ var buffer = file.View.ReadBytes(0, (uint)bufferSize);
+
+ int signaturePos = FindSignature(buffer, FirstFileSignature);
+
+ if (signaturePos == -1)
+ return null;
+
+ if (signaturePos < 8)
+ return null;
+
+ long countOffset = signaturePos - 8;
+ int count = file.View.ReadInt32(countOffset);
+
+ if (!IsSaneCount(count))
+ return null;
+
+ long tableStart = signaturePos;
+ long entrySize = 64;
+ long tableByteSize = count * entrySize;
+
+ long dataBlobStart = tableStart + tableByteSize;
+
+ if (dataBlobStart > file.MaxOffset)
+ return null;
+
+ var dir = new List(count);
+
+ for (int i = 0; i < count; i++)
+ {
+ long entryPos = tableStart + (i * entrySize);
+
+ var nameBytes = file.View.ReadBytes(entryPos, 48);
+
+ // --- FIX APPLIED HERE ---
+ // Replaced Binary.Ascii with System.Text.Encoding.ASCII
+ if (nameBytes.Length >= 4 &&
+ (System.Text.Encoding.ASCII.GetString(nameBytes, 0, 4) == "RIFF" ||
+ System.Text.Encoding.ASCII.GetString(nameBytes, 0, 4) == "OggS"))
+ {
+ break;
+ }
+ // ------------------------
+
+ string name = Binary.GetCString(nameBytes, 0, nameBytes.Length, Encodings.cp932);
+
+ if (string.IsNullOrWhiteSpace(name))
+ continue;
+
+ name = name.TrimEnd('\0');
+
+ uint offset = file.View.ReadUInt32(entryPos + 48);
+ uint size = file.View.ReadUInt32(entryPos + 56);
+
+ if (size == 0)
+ continue;
+
+ var entry = FormatCatalog.Instance.Create(name);
+
+ entry.Offset = dataBlobStart + offset;
+ entry.Size = size;
+
+ if (!entry.CheckPlacement(file.MaxOffset))
+ return null;
+
+ dir.Add(entry);
+ }
+
+ return new ArcFile(file, this, dir);
+ }
+
+ private int FindSignature(byte[] buffer, byte[] signature)
+ {
+ if (buffer.Length < signature.Length) return -1;
+
+ for (int i = 0; i <= buffer.Length - signature.Length; i++)
+ {
+ bool found = true;
+ for (int j = 0; j < signature.Length; j++)
+ {
+ if (buffer[i + j] != signature[j])
+ {
+ found = false;
+ break;
+ }
+ }
+ if (found) return i;
+ }
+ return -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj
index 78d24d21..ffa5f99e 100644
--- a/Legacy/Legacy.csproj
+++ b/Legacy/Legacy.csproj
@@ -100,6 +100,9 @@
+
+
+