(PsbOpener): rewritten with IImageDecoder.

support TLG-based layers.
This commit is contained in:
morkt 2016-10-29 02:06:58 +04:00
parent 559f47c673
commit 75f37f9bfd

View File

@ -41,6 +41,8 @@ namespace GameRes.Formats.Emote
public int Height; public int Height;
public int TruncatedWidth; public int TruncatedWidth;
public int TruncatedHeight; public int TruncatedHeight;
public int OffsetX;
public int OffsetY;
} }
[Serializable] [Serializable]
@ -62,7 +64,7 @@ namespace GameRes.Formats.Emote
public PsbOpener () public PsbOpener ()
{ {
Extensions = new string[] { "psb" }; Extensions = new string[] { "psb", "pimg" };
} }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
@ -77,39 +79,57 @@ namespace GameRes.Formats.Emote
var dir = reader.GetTextures(); var dir = reader.GetTextures();
if (null == dir || 0 == dir.Count) if (null == dir || 0 == dir.Count)
return null; return null;
else return new ArcFile (file, this, dir);
return new ArcFile (file, this, dir);
} }
if (!reader.IsEncrypted) if (!reader.IsEncrypted)
break; break;
} }
if (reader.ParseNonEncrypted())
{
var dir = reader.GetLayers();
if (null == dir)
return null;
return new ArcFile (file, this, dir);
}
return null; return null;
} }
} }
public override Stream OpenEntry (ArcFile arc, Entry entry) public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{ {
var tex = entry as TexEntry; var tex = (TexEntry)entry;
if (null == tex) if ("TLG" == tex.TexType)
return base.OpenEntry (arc, entry); return OpenTlg (arc, tex);
var info = new PsbTexMetaData
byte[] header;
using (var mem = new MemoryStream())
using (var writer = new BinaryWriter (mem))
{ {
writer.Write ((uint)0x81C3D2D1); // 'PSB' ^ 0x81818181 FullWidth = tex.Width,
writer.Write ((int)0); FullHeight = tex.Height,
writer.Write (tex.Width); Width = (uint)tex.TruncatedWidth,
writer.Write (tex.Height); Height = (uint)tex.TruncatedHeight,
writer.Write (tex.TruncatedWidth); TexType = tex.TexType,
writer.Write (tex.TruncatedHeight); BPP = 32
writer.Write (tex.TexType); };
writer.BaseStream.Position = 4;
writer.Write ((int)writer.BaseStream.Length);
header = mem.ToArray();
}
var input = arc.File.CreateStream (entry.Offset, entry.Size); var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new PrefixStream (header, input); return new PsbTextureDecoder (input, info);
}
IImageDecoder OpenTlg (ArcFile arc, TexEntry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
try
{
var info = TlgFormat.ReadMetaData (input);
if (null == info)
throw new InvalidFormatException();
info.OffsetX = entry.OffsetX;
info.OffsetY = entry.OffsetY;
return new ImageFormatDecoder (input, TlgFormat, info);
}
catch
{
input.Dispose();
throw;
}
} }
public override ResourceScheme Scheme public override ResourceScheme Scheme
@ -117,6 +137,10 @@ namespace GameRes.Formats.Emote
get { return new PsbScheme { KnownKeys = KnownKeys }; } get { return new PsbScheme { KnownKeys = KnownKeys }; }
set { KnownKeys = ((PsbScheme)value).KnownKeys; } set { KnownKeys = ((PsbScheme)value).KnownKeys; }
} }
ImageFormat TlgFormat { get { return s_TlgFormat.Value; } }
static Lazy<ImageFormat> s_TlgFormat = new Lazy<ImageFormat> (() => ImageFormat.FindByTag ("TLG"));
} }
/// <summary> /// <summary>
@ -149,6 +173,11 @@ namespace GameRes.Formats.Emote
uint[] m_key = new uint[6]; uint[] m_key = new uint[6];
Dictionary<int, string> m_name_map; Dictionary<int, string> m_name_map;
public bool ParseNonEncrypted ()
{
return Parse (false);
}
public bool Parse (uint key) public bool Parse (uint key)
{ {
m_key[0] = 0x075BCD15; m_key[0] = 0x075BCD15;
@ -158,7 +187,12 @@ namespace GameRes.Formats.Emote
m_key[4] = 0; m_key[4] = 0;
m_key[5] = 0; m_key[5] = 0;
if (!ReadHeader()) return Parse (true);
}
bool Parse (bool encrypted)
{
if (!ReadHeader (encrypted))
return false; return false;
if (Version < 2) if (Version < 2)
throw new NotSupportedException ("Not supported PSB version"); throw new NotSupportedException ("Not supported PSB version");
@ -169,6 +203,36 @@ namespace GameRes.Formats.Emote
return true; return true;
} }
public List<Entry> GetLayers ()
{
var layers = GetRootKey<IList> ("layers");
if (null == layers || 0 == layers.Count)
return null;
var dir = new List<Entry> (layers.Count);
foreach (IDictionary layer in layers)
{
var name = layer["layer_id"].ToString() + ".tlg";
var layer_data = GetRootKey<EmChunk> (name);
if (null == layer_data)
continue;
var entry = new TexEntry {
Name = name,
Type = "image",
Offset = DataOffset + layer_data.Offset,
Size = (uint)layer_data.Length,
TexType = "TLG",
OffsetX = Convert.ToInt32 (layer["left"]),
OffsetY = Convert.ToInt32 (layer["top"]),
Width = Convert.ToInt32 (layer["width"]),
Height = Convert.ToInt32 (layer["height"]),
};
dir.Add (entry);
}
if (0 == dir.Count)
return null;
return dir;
}
public List<Entry> GetTextures () public List<Entry> GetTextures ()
{ {
var source = GetRootKey<IDictionary> ("source"); var source = GetRootKey<IDictionary> ("source");
@ -211,17 +275,16 @@ namespace GameRes.Formats.Emote
int m_root; int m_root;
byte[] m_data; byte[] m_data;
bool ReadHeader () bool ReadHeader (bool encrypted)
{ {
m_input.Position = 4; m_input.Position = 4;
m_version = m_input.ReadUInt16(); m_version = m_input.ReadUInt16();
m_flags = m_input.ReadUInt16(); m_flags = m_input.ReadUInt16();
if (m_version < 3) if (encrypted && m_version < 3)
m_flags = 2; m_flags = 2;
var header = new byte[0x20]; var header = m_input.ReadBytes (0x20);
m_input.Read (header, 0, header.Length); if (encrypted && 0 != (m_flags & 1))
if (0 != (m_flags & 1))
Decrypt (header, 0, 0x20); Decrypt (header, 0, 0x20);
m_names = LittleEndian.ToInt32 (header, 0x04); m_names = LittleEndian.ToInt32 (header, 0x04);
@ -246,7 +309,7 @@ namespace GameRes.Formats.Emote
m_data = new byte[m_chunk_data]; m_data = new byte[m_chunk_data];
int data_pos = (int)m_input.Position; int data_pos = (int)m_input.Position;
m_input.Read (m_data, data_pos, m_chunk_data-data_pos); m_input.Read (m_data, data_pos, m_chunk_data-data_pos);
if (0 != (m_flags & 2)) if (encrypted && 0 != (m_flags & 2))
Decrypt (m_data, m_names, m_chunk_offsets-m_names); Decrypt (m_data, m_names, m_chunk_offsets-m_names);
// root object is a dictionary // root object is a dictionary
return 0x21 == m_data[m_root]; return 0x21 == m_data[m_root];
@ -559,79 +622,58 @@ namespace GameRes.Formats.Emote
public string TexType; public string TexType;
public int FullWidth; public int FullWidth;
public int FullHeight; public int FullHeight;
public int DataOffset;
} }
/// <summary> /// <summary>
/// Artificial format representing PSB texture. /// Artificial format representing PSB texture.
/// </summary> /// </summary>
[Export(typeof(ImageFormat))] internal sealed class PsbTextureDecoder : BinaryImageDecoder
internal class PsbTextureFormat : ImageFormat
{ {
public override string Tag { get { return "PSB/TEXTURE"; } } PsbTexMetaData m_info;
public override string Description { get { return "PSB texture format"; } }
public override uint Signature { get { return 0x81C3D2D1; } } // 'PSB' ^ 0x81818181
public PsbTextureFormat () public PsbTextureDecoder (IBinaryStream input, PsbTexMetaData info) : base (input, info)
{ {
Extensions = new string[0]; m_input = input;
m_info = info;
} }
public override ImageMetaData ReadMetaData (IBinaryStream stream) protected override ImageData GetImageData ()
{ {
stream.Position = 4; var pixels = new byte[m_info.Width * m_info.Height * 4];
// need BinaryReader because of ReadString if ("RGBA8" == m_info.TexType)
using (var reader = new BinaryReader (stream.AsStream, Encoding.UTF8, true)) ReadRgba8 (pixels);
{ else if ("RGBA4444" == m_info.TexType)
var info = new PsbTexMetaData { BPP = 32 }; ReadRgba4444 (pixels);
info.DataOffset = reader.ReadInt32();
info.FullWidth = reader.ReadInt32();
info.FullHeight = reader.ReadInt32();
info.Width = reader.ReadUInt32();
info.Height = reader.ReadUInt32();
info.TexType = reader.ReadString();
return info;
}
}
public override ImageData Read (IBinaryStream stream, ImageMetaData info)
{
var meta = (PsbTexMetaData)info;
var pixels = new byte[meta.Width * meta.Height * 4];
if ("RGBA8" == meta.TexType)
ReadRgba8 (stream.AsStream, meta, pixels);
else if ("RGBA4444" == meta.TexType)
ReadRgba4444 (stream.AsStream, meta, pixels);
else else
throw new NotImplementedException (string.Format ("PSB texture format '{0}' not implemented", meta.TexType)); throw new NotImplementedException (string.Format ("PSB texture format '{0}' not implemented", m_info.TexType));
return ImageData.Create (info, PixelFormats.Bgra32, null, pixels); return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels);
} }
void ReadRgba8 (Stream input, PsbTexMetaData meta, byte[] output) void ReadRgba8 (byte[] output)
{ {
int dst_stride = (int)meta.Width * 4; int dst_stride = (int)m_info.Width * 4;
long next_row = meta.DataOffset; long next_row = 0;
int src_stride = meta.FullWidth * 4; int src_stride = m_info.FullWidth * 4;
int dst = 0; int dst = 0;
for (uint i = 0; i < meta.Height; ++i) for (uint i = 0; i < m_info.Height; ++i)
{ {
input.Position = next_row; m_input.Position = next_row;
input.Read (output, dst, dst_stride); m_input.Read (output, dst, dst_stride);
dst += dst_stride; dst += dst_stride;
next_row += src_stride; next_row += src_stride;
} }
} }
void ReadRgba4444 (Stream input, PsbTexMetaData meta, byte[] output) void ReadRgba4444 (byte[] output)
{ {
int dst_stride = (int)meta.Width * 4; int dst_stride = (int)m_info.Width * 4;
int src_stride = meta.FullWidth * 2; int src_stride = m_info.FullWidth * 2;
int dst = 0; int dst = 0;
var row = new byte[src_stride]; var row = new byte[src_stride];
input.Position = meta.DataOffset; m_input.Position = 0;
for (uint i = 0; i < meta.Height; ++i) for (uint i = 0; i < m_info.Height; ++i)
{ {
input.Read (row, 0, src_stride); m_input.Read (row, 0, src_stride);
int src = 0; int src = 0;
for (int x = 0; x < dst_stride; x += 4) for (int x = 0; x < dst_stride; x += 4)
{ {
@ -644,10 +686,5 @@ namespace GameRes.Formats.Emote
} }
} }
} }
public override void Write (Stream file, ImageData image)
{
throw new NotSupportedException ("PsbTextureFormat.Write not supported");
}
} }
} }