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 @@ + + +