mirror of
https://github.com/crskycode/GARbro.git
synced 2026-07-08 01:30:18 +08:00
Formats update.
(HG3): attempt to recognize swapped Red/Blue channels. Fairytale BDT archives. (XP3, VF): add 'exe' to extensions list. (DXR): consolidated Macromedia Director archives. (SND): support mp3 streams. (QPK): PSP resource archive. (GRC): support encrypted images.
This commit is contained in:
@@ -23,13 +23,13 @@
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using GameRes.Compression;
|
||||
using GameRes.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
@@ -46,12 +46,12 @@ namespace GameRes.Formats.Macromedia
|
||||
|
||||
public DxrOpener ()
|
||||
{
|
||||
Extensions = new[] { "dxr", "cxt" };
|
||||
Extensions = new[] { "dxr", "cxt", "cct", "dcr" };
|
||||
Signatures = new[] { 0x52494658u, 0x58464952u };
|
||||
}
|
||||
|
||||
internal static readonly HashSet<string> RawChunks = new HashSet<string> {
|
||||
"RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", "snd "
|
||||
"RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", //"snd "
|
||||
};
|
||||
|
||||
internal bool ConvertText = true;
|
||||
@@ -71,9 +71,9 @@ namespace GameRes.Formats.Macromedia
|
||||
|
||||
var dir = new List<Entry> ();
|
||||
ImportMedia (dir_file, dir);
|
||||
foreach (MemoryMapEntry entry in dir_file.MMap.Dir)
|
||||
foreach (DirectorEntry entry in dir_file.Directory)
|
||||
{
|
||||
if (entry.Size != 0 && RawChunks.Contains (entry.FourCC))
|
||||
if (entry.Size != 0 && entry.Offset != -1 && RawChunks.Contains (entry.FourCC))
|
||||
{
|
||||
entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim());
|
||||
if ("snd " == entry.FourCC)
|
||||
@@ -90,19 +90,30 @@ namespace GameRes.Formats.Macromedia
|
||||
var snd = entry as SoundEntry;
|
||||
if (snd != null)
|
||||
return OpenSound (arc, snd);
|
||||
var ment = entry as MemoryMapEntry;
|
||||
if (!ConvertText || null == ment || ment.FourCC != "STXT")
|
||||
var pent = entry as PackedEntry;
|
||||
if (null == pent)
|
||||
return base.OpenEntry (arc, entry);
|
||||
uint offset = Binary.BigEndian (arc.File.View.ReadUInt32 (entry.Offset));
|
||||
uint length = Binary.BigEndian (arc.File.View.ReadUInt32 (entry.Offset + 4));
|
||||
return arc.File.CreateStream (entry.Offset + offset, length);
|
||||
var input = OpenChunkStream (arc.File, pent);
|
||||
var ment = entry as DirectorEntry;
|
||||
if (null == ment || !ConvertText || ment.FourCC != "STXT")
|
||||
return input.AsStream;
|
||||
using (input)
|
||||
{
|
||||
uint offset = Binary.BigEndian (input.ReadUInt32());
|
||||
uint length = Binary.BigEndian (input.ReadUInt32());
|
||||
input.Position = offset;
|
||||
var text = input.ReadBytes ((int)length);
|
||||
return new BinMemoryStream (text, entry.Name);
|
||||
}
|
||||
}
|
||||
|
||||
internal Stream OpenSound (ArcFile arc, SoundEntry entry)
|
||||
{
|
||||
if (null == entry.Header)
|
||||
return base.OpenEntry (arc, entry);
|
||||
var header = arc.File.View.ReadBytes (entry.Header.Offset, entry.Header.Size);
|
||||
var header = new byte[entry.Header.UnpackedSize];
|
||||
using (var input = OpenChunkStream (arc.File, entry.Header))
|
||||
input.Read (header, 0, header.Length);
|
||||
var format = entry.DeserializeHeader (header);
|
||||
var riff = new MemoryStream (0x2C);
|
||||
WaveAudio.WriteRiffHeader (riff, format, entry.Size);
|
||||
@@ -110,12 +121,14 @@ namespace GameRes.Formats.Macromedia
|
||||
{
|
||||
using (riff)
|
||||
{
|
||||
var input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
var input = OpenChunkStream (arc.File, entry).AsStream;
|
||||
return new PrefixStream (riff.ToArray(), input);
|
||||
}
|
||||
}
|
||||
// samples are stored in big-endian format
|
||||
var samples = arc.File.View.ReadBytes (entry.Offset, entry.Size);
|
||||
var samples = new byte[entry.UnpackedSize];
|
||||
using (var input = OpenChunkStream (arc.File, entry))
|
||||
input.Read (samples, 0, samples.Length);
|
||||
for (int i = 1; i < samples.Length; i += 2)
|
||||
{
|
||||
byte s = samples[i-1];
|
||||
@@ -130,7 +143,6 @@ namespace GameRes.Formats.Macromedia
|
||||
void ImportMedia (DirectorFile dir_file, List<Entry> dir)
|
||||
{
|
||||
var seen_ids = new HashSet<int>();
|
||||
var mmap = dir_file.MMap;
|
||||
foreach (var cast in dir_file.Casts)
|
||||
{
|
||||
foreach (var piece in cast.Members.Values)
|
||||
@@ -157,17 +169,35 @@ namespace GameRes.Formats.Macromedia
|
||||
{
|
||||
if ("ediM" == elem.FourCC)
|
||||
{
|
||||
var ediM = dir_file.MMap[elem.Id];
|
||||
if (string.IsNullOrEmpty (name))
|
||||
name = ediM.Id.ToString ("D6");
|
||||
return new Entry
|
||||
var ediM = dir_file.Index[elem.Id];
|
||||
name = SanitizeName(name, ediM.Id);
|
||||
return new PackedEntry
|
||||
{
|
||||
Name = name + ".mp3",
|
||||
Type = "audio",
|
||||
Offset = ediM.Offset,
|
||||
Size = ediM.Size,
|
||||
Name = name + ".ediM",
|
||||
Type = "audio",
|
||||
Offset = ediM.Offset,
|
||||
Size = ediM.Size,
|
||||
UnpackedSize = ediM.UnpackedSize,
|
||||
IsPacked = ediM.IsPacked
|
||||
};
|
||||
}
|
||||
else if ("snd " == elem.FourCC)
|
||||
{
|
||||
var snd = dir_file.Index[elem.Id];
|
||||
if (snd.Size != 0)
|
||||
{
|
||||
name = SanitizeName (name, snd.Id);
|
||||
return new PackedEntry
|
||||
{
|
||||
Name = name + ".snd",
|
||||
Type = "audio",
|
||||
Offset = snd.Offset,
|
||||
Size = snd.Size,
|
||||
UnpackedSize = snd.Size,
|
||||
IsPacked = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (null == sndHrec && "sndH" == elem.FourCC)
|
||||
sndHrec = elem;
|
||||
else if (null == sndSrec && "sndS" == elem.FourCC)
|
||||
@@ -175,65 +205,99 @@ namespace GameRes.Formats.Macromedia
|
||||
}
|
||||
if (sndHrec == null || sndSrec == null)
|
||||
return null;
|
||||
var sndH = dir_file.MMap[sndHrec.Id];
|
||||
var sndS = dir_file.MMap[sndSrec.Id];
|
||||
if (string.IsNullOrEmpty (name))
|
||||
name = sndSrec.Id.ToString ("D6");
|
||||
var sndH = dir_file.Index[sndHrec.Id];
|
||||
var sndS = dir_file.Index[sndSrec.Id];
|
||||
name = SanitizeName (name, sndSrec.Id);
|
||||
return new SoundEntry
|
||||
{
|
||||
Name = name + ".snd",
|
||||
Type = "audio",
|
||||
Offset = sndS.Offset,
|
||||
Size = sndS.Size,
|
||||
UnpackedSize = sndS.UnpackedSize,
|
||||
IsPacked = sndS.IsPacked,
|
||||
Header = sndH,
|
||||
};
|
||||
}
|
||||
|
||||
Entry ImportBitmap (CastMember bitmap, DirectorFile dir_file, Cast cast)
|
||||
{
|
||||
// var bitd = dir_file.KeyTable.FindByCast (bitmap.Id, "BITD");
|
||||
KeyTableEntry bitd = null, alfa = null;
|
||||
KeyTableEntry bitd = null, edim = null, alfa = null;
|
||||
foreach (var elem in dir_file.KeyTable.Table.Where (e => e.CastId == bitmap.Id))
|
||||
{
|
||||
if (null == bitd && "BITD" == elem.FourCC)
|
||||
bitd = elem;
|
||||
else if (null == edim && "ediM" == elem.FourCC)
|
||||
edim = elem;
|
||||
else if (null == alfa && "ALFA" == elem.FourCC)
|
||||
alfa = elem;
|
||||
}
|
||||
if (bitd == null)
|
||||
if (bitd == null && edim == null)
|
||||
return null;
|
||||
var entry = new BitmapEntry();
|
||||
entry.DeserializeHeader (bitmap.SpecificData);
|
||||
var name = bitmap.Info.Name;
|
||||
if (string.IsNullOrEmpty (name))
|
||||
name = bitd.Id.ToString ("D6");
|
||||
var chunk = dir_file.MMap[bitd.Id];
|
||||
entry.Name = name + ".BITD";
|
||||
entry.Type = "image";
|
||||
entry.Offset = chunk.Offset;
|
||||
entry.Size = chunk.Size;
|
||||
if (entry.Palette > 0)
|
||||
if (bitd != null)
|
||||
{
|
||||
var cast_id = cast.Index[entry.Palette-1];
|
||||
var clut = dir_file.KeyTable.FindByCast (cast_id, "CLUT");
|
||||
if (clut != null)
|
||||
entry.PaletteRef = dir_file.MMap[clut.Id];
|
||||
entry.DeserializeHeader (bitmap.SpecificData);
|
||||
var name = SanitizeName (bitmap.Info.Name, bitd.Id);
|
||||
var chunk = dir_file.Index[bitd.Id];
|
||||
entry.Name = name + ".BITD";
|
||||
entry.Type = "image";
|
||||
entry.Offset = chunk.Offset;
|
||||
entry.Size = chunk.Size;
|
||||
entry.IsPacked = chunk.IsPacked;
|
||||
entry.UnpackedSize = chunk.UnpackedSize;
|
||||
if (entry.Palette > 0)
|
||||
{
|
||||
var cast_id = cast.Index[entry.Palette-1];
|
||||
var clut = dir_file.KeyTable.FindByCast (cast_id, "CLUT");
|
||||
if (clut != null)
|
||||
entry.PaletteRef = dir_file.Index[clut.Id];
|
||||
}
|
||||
}
|
||||
else // if (edim != null)
|
||||
{
|
||||
var name = SanitizeName (bitmap.Info.Name, edim.Id);
|
||||
var chunk = dir_file.Index[edim.Id];
|
||||
entry.Name = name + ".jpg";
|
||||
entry.Type = "image";
|
||||
entry.Offset = chunk.Offset;
|
||||
entry.Size = chunk.Size;
|
||||
entry.IsPacked = false;
|
||||
entry.UnpackedSize = entry.Size;
|
||||
}
|
||||
if (alfa != null)
|
||||
entry.AlphaRef = dir_file.MMap[alfa.Id];
|
||||
entry.AlphaRef = dir_file.Index[alfa.Id];
|
||||
return entry;
|
||||
}
|
||||
|
||||
static readonly Regex ForbiddenCharsRe = new Regex (@"[:?*<>/\\]");
|
||||
|
||||
string SanitizeName (string name, int id)
|
||||
{
|
||||
name = name?.Trim();
|
||||
if (string.IsNullOrEmpty (name))
|
||||
name = id.ToString ("D6");
|
||||
else
|
||||
name = ForbiddenCharsRe.Replace (name, "_");
|
||||
return name;
|
||||
}
|
||||
|
||||
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
|
||||
{
|
||||
var bent = entry as BitmapEntry;
|
||||
if (null == bent)
|
||||
return base.OpenImage (arc, entry);
|
||||
return base.OpenImage(arc, entry);
|
||||
if (entry.Name.HasExtension (".jpg"))
|
||||
return OpenJpeg (arc, bent);
|
||||
|
||||
BitmapPalette palette = null;
|
||||
if (bent.PaletteRef != null)
|
||||
{
|
||||
var pal_bytes = arc.File.View.ReadBytes (bent.PaletteRef.Offset, bent.PaletteRef.Size);
|
||||
palette = ReadPalette (pal_bytes);
|
||||
using (var pal = OpenChunkStream (arc.File, bent.PaletteRef))
|
||||
{
|
||||
var pal_bytes = pal.ReadBytes ((int)bent.PaletteRef.UnpackedSize);
|
||||
palette = ReadPalette (pal_bytes);
|
||||
}
|
||||
}
|
||||
else if (bent.BitDepth <= 8)
|
||||
{
|
||||
@@ -247,27 +311,53 @@ namespace GameRes.Formats.Macromedia
|
||||
case -101: palette = Palettes.SystemWindows; break;
|
||||
}
|
||||
}
|
||||
var info = new ImageMetaData {
|
||||
var info = new BitdMetaData {
|
||||
Width = (uint)(bent.Right - bent.Left),
|
||||
Height = (uint)(bent.Bottom - bent.Top),
|
||||
BPP = bent.BitDepth
|
||||
BPP = bent.BitDepth,
|
||||
DepthType = bent.DepthType,
|
||||
};
|
||||
byte[] alpha_channel = null;
|
||||
if (bent.AlphaRef != null)
|
||||
{
|
||||
using (var alpha = arc.File.CreateStream (bent.AlphaRef.Offset, bent.AlphaRef.Size))
|
||||
{
|
||||
var alpha_info = new ImageMetaData {
|
||||
Width = info.Width,
|
||||
Height = info.Height,
|
||||
BPP = 8,
|
||||
};
|
||||
var decoder = new BitdDecoder (alpha, alpha_info, null);
|
||||
alpha_channel = decoder.Unpack8bpp();
|
||||
}
|
||||
}
|
||||
alpha_channel = ReadAlphaChannel (arc.File, bent.AlphaRef, info);
|
||||
var input = OpenChunkStream (arc.File, bent).AsStream;
|
||||
return new BitdDecoder (input, info, palette) { AlphaChannel = alpha_channel };
|
||||
}
|
||||
|
||||
IImageDecoder OpenJpeg (ArcFile arc, BitmapEntry entry)
|
||||
{
|
||||
if (null == entry.AlphaRef)
|
||||
return base.OpenImage (arc, entry);
|
||||
// jpeg with alpha-channel
|
||||
var input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
return new BitdDecoder (input.AsStream, info, palette) { AlphaChannel = alpha_channel };
|
||||
try
|
||||
{
|
||||
var info = ImageFormat.Jpeg.ReadMetaData (input);
|
||||
if (null == info)
|
||||
throw new InvalidFormatException ("Invalid 'ediM' chunk.");
|
||||
var alpha_channel = ReadAlphaChannel (arc.File, entry.AlphaRef, info);
|
||||
return BitdDecoder.FromJpeg (input, info, alpha_channel);
|
||||
}
|
||||
catch
|
||||
{
|
||||
input.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] ReadAlphaChannel (ArcView file, DirectorEntry entry, ImageMetaData info)
|
||||
{
|
||||
using (var alpha = OpenChunkStream (file, entry))
|
||||
{
|
||||
var alpha_info = new BitdMetaData {
|
||||
Width = info.Width,
|
||||
Height = info.Height,
|
||||
BPP = 8,
|
||||
DepthType = 0x80,
|
||||
};
|
||||
var decoder = new BitdDecoder (alpha.AsStream, alpha_info, null);
|
||||
return decoder.Unpack8bpp();
|
||||
}
|
||||
}
|
||||
|
||||
BitmapPalette ReadPalette (byte[] data)
|
||||
@@ -280,9 +370,20 @@ namespace GameRes.Formats.Macromedia
|
||||
}
|
||||
return new BitmapPalette (colors);
|
||||
}
|
||||
|
||||
IBinaryStream OpenChunkStream (ArcView file, PackedEntry entry)
|
||||
{
|
||||
var input = file.CreateStream (entry.Offset, entry.Size);
|
||||
if (!entry.IsPacked)
|
||||
return input;
|
||||
var data = new byte[entry.UnpackedSize];
|
||||
using (var zstream = new ZLibStream (input, CompressionMode.Decompress))
|
||||
zstream.Read (data, 0, data.Length);
|
||||
return new BinMemoryStream (data, entry.Name);
|
||||
}
|
||||
}
|
||||
|
||||
internal class BitmapEntry : Entry
|
||||
internal class BitmapEntry : PackedEntry
|
||||
{
|
||||
public byte Flags;
|
||||
public byte DepthType;
|
||||
@@ -292,8 +393,8 @@ namespace GameRes.Formats.Macromedia
|
||||
public int Right;
|
||||
public int BitDepth;
|
||||
public int Palette;
|
||||
public Entry PaletteRef;
|
||||
public Entry AlphaRef;
|
||||
public DirectorEntry PaletteRef;
|
||||
public DirectorEntry AlphaRef;
|
||||
|
||||
public void DeserializeHeader (byte[] data)
|
||||
{
|
||||
@@ -314,9 +415,9 @@ namespace GameRes.Formats.Macromedia
|
||||
}
|
||||
}
|
||||
|
||||
internal class SoundEntry : Entry
|
||||
internal class SoundEntry : PackedEntry
|
||||
{
|
||||
public Entry Header;
|
||||
public DirectorEntry Header;
|
||||
|
||||
public WaveFormat DeserializeHeader (byte[] header)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user