mirror of
https://github.com/crskycode/GARbro.git
synced 2026-07-08 01:30:18 +08:00
Add support for Kid P2T files, Broccoli Pak/GRP/MPEG and Leaf Video
This commit is contained in:
@@ -161,6 +161,7 @@
|
|||||||
<Compile Include="Ism\ImagePNG.cs" />
|
<Compile Include="Ism\ImagePNG.cs" />
|
||||||
<Compile Include="Kid\ArcDATRAW.cs" />
|
<Compile Include="Kid\ArcDATRAW.cs" />
|
||||||
<Compile Include="Kid\ArcKLZ.cs" />
|
<Compile Include="Kid\ArcKLZ.cs" />
|
||||||
|
<Compile Include="Kid\ArcP2T.cs" />
|
||||||
<Compile Include="Kid\ImageBIP.cs" />
|
<Compile Include="Kid\ImageBIP.cs" />
|
||||||
<Compile Include="Kid\ImageBIParc.cs" />
|
<Compile Include="Kid\ImageBIParc.cs" />
|
||||||
<Compile Include="Kid\ImageKLZ.cs" />
|
<Compile Include="Kid\ImageKLZ.cs" />
|
||||||
@@ -201,6 +202,7 @@
|
|||||||
<Compile Include="ArcARCX.cs" />
|
<Compile Include="ArcARCX.cs" />
|
||||||
<Compile Include="ArcASAR.cs" />
|
<Compile Include="ArcASAR.cs" />
|
||||||
<Compile Include="Artemis\ArcMJA.cs" />
|
<Compile Include="Artemis\ArcMJA.cs" />
|
||||||
|
<Compile Include="Leaf\LeafVideo.cs" />
|
||||||
<Compile Include="Macintosh\ImagePICT.cs" />
|
<Compile Include="Macintosh\ImagePICT.cs" />
|
||||||
<Compile Include="Macromedia\ArcDXR.cs" />
|
<Compile Include="Macromedia\ArcDXR.cs" />
|
||||||
<Compile Include="Macromedia\AudioSND.cs" />
|
<Compile Include="Macromedia\AudioSND.cs" />
|
||||||
@@ -218,6 +220,7 @@
|
|||||||
<Compile Include="NipponIchi\ArcPSFS.cs" />
|
<Compile Include="NipponIchi\ArcPSFS.cs" />
|
||||||
<Compile Include="NipponIchi\ImageNMT.cs" />
|
<Compile Include="NipponIchi\ImageNMT.cs" />
|
||||||
<Compile Include="NScripter\Script.cs" />
|
<Compile Include="NScripter\Script.cs" />
|
||||||
|
<Compile Include="Ps1\ImageTIM.cs" />
|
||||||
<Compile Include="Psp\ArcQPK.cs" />
|
<Compile Include="Psp\ArcQPK.cs" />
|
||||||
<Compile Include="ScrPlayer\ImageIMG.cs" />
|
<Compile Include="ScrPlayer\ImageIMG.cs" />
|
||||||
<Compile Include="SingleFileArchive.cs" />
|
<Compile Include="SingleFileArchive.cs" />
|
||||||
|
|||||||
68
ArcFormats/Leaf/LeafVideo.cs
Normal file
68
ArcFormats/Leaf/LeafVideo.cs
Normal file
@@ -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> { entry });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
Legacy/Broccoli/BroccoliGrp.cs
Normal file
144
Legacy/Broccoli/BroccoliGrp.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Legacy/Broccoli/BroccoliMpeg.cs
Normal file
35
Legacy/Broccoli/BroccoliMpeg.cs
Normal file
@@ -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> { entry });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
Legacy/Broccoli/BroccoliPak.cs
Normal file
119
Legacy/Broccoli/BroccoliPak.cs
Normal file
@@ -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<Entry>(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<Entry>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,6 +100,9 @@
|
|||||||
<Compile Include="Blucky\Aliases.cs" />
|
<Compile Include="Blucky\Aliases.cs" />
|
||||||
<Compile Include="Bom\ImageGRP.cs" />
|
<Compile Include="Bom\ImageGRP.cs" />
|
||||||
<Compile Include="Broccoli\ArcP00.cs" />
|
<Compile Include="Broccoli\ArcP00.cs" />
|
||||||
|
<Compile Include="Broccoli\BroccoliGrp.cs" />
|
||||||
|
<Compile Include="Broccoli\BroccoliMpeg.cs" />
|
||||||
|
<Compile Include="Broccoli\BroccoliPak.cs" />
|
||||||
<Compile Include="CottonClub\ImageLMG.cs" />
|
<Compile Include="CottonClub\ImageLMG.cs" />
|
||||||
<Compile Include="Desire\ArcDSV.cs" />
|
<Compile Include="Desire\ArcDSV.cs" />
|
||||||
<Compile Include="Desire\ImageDES.cs" />
|
<Compile Include="Desire\ImageDES.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user