mirror of
https://github.com/crskycode/GARbro.git
synced 2024-11-27 15:44:00 +08:00
implemented CPK archives, XTX images and HCA audio.
This commit is contained in:
parent
b41f93cd0e
commit
ed365530a6
@ -66,6 +66,10 @@
|
||||
<Compile Include="Abel\ImageGPS.cs" />
|
||||
<Compile Include="Actgs\ArcDAT.cs" />
|
||||
<Compile Include="ArcCG.cs" />
|
||||
<Compile Include="Cri\ArcCPK.cs" />
|
||||
<Compile Include="Cri\AudioHCA.cs" />
|
||||
<Compile Include="Cri\BigEndianReader.cs" />
|
||||
<Compile Include="Cri\ImageXTX.cs" />
|
||||
<Compile Include="Entis\ErisaMatrix.cs" />
|
||||
<Compile Include="ImageLZ.cs" />
|
||||
<Compile Include="NitroPlus\ArcNPK.cs" />
|
||||
|
416
ArcFormats/Cri/ArcCPK.cs
Normal file
416
ArcFormats/Cri/ArcCPK.cs
Normal file
@ -0,0 +1,416 @@
|
||||
//! \file ArcCPK.cs
|
||||
//! \date Mon Feb 29 12:39:36 2016
|
||||
//! \brief CRI Middleware resource archive.
|
||||
//
|
||||
// Copyright (C) 2016 by morkt
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Cri
|
||||
{
|
||||
using TableRow = Dictionary<string, object>;
|
||||
|
||||
internal class CpkEntry : PackedEntry
|
||||
{
|
||||
public int Id;
|
||||
}
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class CpkOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "CPK"; } }
|
||||
public override string Description { get { return "CRI Middleware resource archive"; } }
|
||||
public override uint Signature { get { return 0x204B5043; } } // 'CPK '
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanCreate { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
var reader = new IndexReader (file);
|
||||
var dir = reader.ReadIndex();
|
||||
if (null == dir || !dir.Any())
|
||||
return null;
|
||||
if (!reader.HasNames)
|
||||
DetectFileTypes (file, dir);
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
if (entry.Size < 0x10 || !arc.File.View.AsciiEqual (entry.Offset, "CRILAYLA"))
|
||||
return base.OpenEntry (arc, entry);
|
||||
|
||||
var unpacked_size = arc.File.View.ReadInt32 (entry.Offset+8);
|
||||
var packed_size = arc.File.View.ReadUInt32 (entry.Offset+12);
|
||||
if (unpacked_size < 0 || packed_size > entry.Size - 0x10)
|
||||
return base.OpenEntry (arc, entry);
|
||||
uint prefix_size = entry.Size - (0x10+packed_size);
|
||||
var output = new byte[unpacked_size+prefix_size];
|
||||
var packed = arc.File.View.ReadBytes (entry.Offset+0x10, packed_size);
|
||||
Array.Reverse (packed);
|
||||
using (var mem = new MemoryStream (packed))
|
||||
using (var input = new MsbBitStream (mem))
|
||||
{
|
||||
byte[] sizes = { 2, 3, 5, 8 };
|
||||
int dst = (int)prefix_size;
|
||||
while (dst < output.Length)
|
||||
{
|
||||
if (0 == input.GetNextBit())
|
||||
{
|
||||
output[dst++] = (byte)input.GetBits (8);
|
||||
continue;
|
||||
}
|
||||
int count = 3;
|
||||
int offset = input.GetBits (13) + 3;
|
||||
int rank = 0;
|
||||
int bits, step;
|
||||
do
|
||||
{
|
||||
bits = sizes[rank];
|
||||
step = input.GetBits (bits);
|
||||
count += step;
|
||||
if (rank < 3)
|
||||
rank++;
|
||||
}
|
||||
while (((1 << bits) - 1) == step);
|
||||
Binary.CopyOverlapped (output, dst-offset, dst, count);
|
||||
dst += count;
|
||||
}
|
||||
}
|
||||
Array.Reverse (output, (int)prefix_size, unpacked_size);
|
||||
arc.File.View.Read (entry.Offset+0x10+packed_size, output, 0, prefix_size);
|
||||
return new MemoryStream (output);
|
||||
}
|
||||
|
||||
void DetectFileTypes (ArcView file, List<Entry> dir)
|
||||
{
|
||||
foreach (var entry in dir)
|
||||
{
|
||||
var offset = entry.Offset;
|
||||
var signature = file.View.ReadUInt32 (offset);
|
||||
if (entry.Size > 0x10 && 0x4C495243 == signature) // 'CRIL'
|
||||
{
|
||||
uint packed_size = file.View.ReadUInt32 (offset+12);
|
||||
if (packed_size < entry.Size - 0x10)
|
||||
{
|
||||
signature = file.View.ReadUInt32 (offset+0x10+packed_size);
|
||||
if (0x10 == signature)
|
||||
signature = file.View.ReadUInt32 (offset+0x10+packed_size+signature);
|
||||
}
|
||||
}
|
||||
var res = AutoEntry.DetectFileType (signature);
|
||||
if (null != res)
|
||||
{
|
||||
entry.Type = res.Type;
|
||||
entry.Name = Path.ChangeExtension (entry.Name, res.Extensions.FirstOrDefault());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class IndexReader
|
||||
{
|
||||
ArcView m_file;
|
||||
Deserializer m_des = new Deserializer();
|
||||
long m_content_offset;
|
||||
Dictionary<int, Entry> m_dir = new Dictionary<int, Entry>();
|
||||
|
||||
public bool HasNames { get; private set; }
|
||||
|
||||
public IndexReader (ArcView file)
|
||||
{
|
||||
m_file = file;
|
||||
}
|
||||
|
||||
public List<Entry> ReadIndex ()
|
||||
{
|
||||
var chunk = ReadUTFChunk (4);
|
||||
var header = m_des.DeserializeUTFChunk (chunk).First();
|
||||
|
||||
m_content_offset = (long)header["ContentOffset"];
|
||||
HasNames = header.ContainsKey ("TocOffset");
|
||||
if (HasNames)
|
||||
{
|
||||
ReadToc ((long)header["TocOffset"]);
|
||||
}
|
||||
if (header.ContainsKey ("ItocOffset"))
|
||||
{
|
||||
var align = (uint)(int)header["Align"];
|
||||
ReadItoc ((long)header["ItocOffset"], align);
|
||||
}
|
||||
return m_dir.Values.ToList();
|
||||
}
|
||||
|
||||
void ReadToc (long toc_offset)
|
||||
{
|
||||
var base_offset = Math.Min (m_content_offset, toc_offset);
|
||||
if (!m_file.View.AsciiEqual (toc_offset, "TOC "))
|
||||
throw new InvalidFormatException();
|
||||
var chunk = ReadUTFChunk (toc_offset+4);
|
||||
var table = m_des.DeserializeUTFChunk (chunk);
|
||||
foreach (var row in table)
|
||||
{
|
||||
var entry = new CpkEntry
|
||||
{
|
||||
Id = (int)row["ID"],
|
||||
Offset = (long)row["FileOffset"] + base_offset,
|
||||
Size = (uint)(int)row["FileSize"],
|
||||
};
|
||||
if (row.ContainsKey ("ExtractSize"))
|
||||
entry.UnpackedSize = (uint)(int)row["ExtractSize"];
|
||||
else
|
||||
entry.UnpackedSize = entry.Size;
|
||||
entry.IsPacked = entry.Size != entry.UnpackedSize;
|
||||
var name = (string)row["FileName"];
|
||||
if (row.ContainsKey ("DirName"))
|
||||
name = Path.Combine ((string)row["DirName"], name);
|
||||
entry.Name = name;
|
||||
entry.Type = FormatCatalog.Instance.GetTypeFromName (name);
|
||||
m_dir[entry.Id] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadItoc (long toc_offset, uint align)
|
||||
{
|
||||
if (!m_file.View.AsciiEqual (toc_offset, "ITOC"))
|
||||
throw new InvalidFormatException();
|
||||
var chunk = ReadUTFChunk (toc_offset+4);
|
||||
var itoc = m_des.DeserializeUTFChunk (chunk).FirstOrDefault();
|
||||
if (null == itoc || !itoc.ContainsKey ("DataL"))
|
||||
return;
|
||||
|
||||
var dataL = m_des.DeserializeUTFChunk ((byte[])itoc["DataL"]);
|
||||
var dataH = m_des.DeserializeUTFChunk ((byte[])itoc["DataH"]);
|
||||
foreach (var row in dataL.Concat (dataH))
|
||||
{
|
||||
int id = (int)row["ID"];
|
||||
var entry = GetEntryById (id);
|
||||
entry.Size = (uint)(int)row["FileSize"];
|
||||
if (row.ContainsKey ("ExtractSize"))
|
||||
entry.UnpackedSize = (uint)(int)row["ExtractSize"];
|
||||
else
|
||||
entry.UnpackedSize = entry.Size;
|
||||
entry.IsPacked = entry.Size != entry.UnpackedSize;
|
||||
}
|
||||
long current_offset = m_content_offset;
|
||||
foreach (var id in m_dir.Keys.OrderBy (x => x))
|
||||
{
|
||||
var entry = m_dir[id];
|
||||
entry.Offset = current_offset;
|
||||
current_offset += entry.Size;
|
||||
if (align != 0)
|
||||
{
|
||||
var tail = entry.Size % align;
|
||||
if (tail > 0)
|
||||
current_offset += align - tail;
|
||||
}
|
||||
if (string.IsNullOrEmpty (entry.Name))
|
||||
entry.Name = id.ToString ("D5");
|
||||
}
|
||||
}
|
||||
|
||||
CpkEntry GetEntryById (int id)
|
||||
{
|
||||
Entry entry;
|
||||
if (!m_dir.TryGetValue (id, out entry))
|
||||
{
|
||||
entry = new CpkEntry { Id = id };
|
||||
m_dir[id] = entry;
|
||||
}
|
||||
return entry as CpkEntry;
|
||||
}
|
||||
|
||||
byte[] ReadUTFChunk (long offset)
|
||||
{
|
||||
long chunk_size = m_file.View.ReadInt64 (offset+4);
|
||||
if (chunk_size < 0 || chunk_size > int.MaxValue)
|
||||
throw new FileSizeException();
|
||||
var chunk = m_file.View.ReadBytes (offset+12, (uint)chunk_size);
|
||||
if (chunk.Length < chunk_size)
|
||||
throw new EndOfStreamException ("Unexpected end of file");
|
||||
if (!Binary.AsciiEqual (chunk, 0, "@UTF"))
|
||||
DecryptUTFChunk (chunk);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
internal static void DecryptUTFChunk (byte[] chunk)
|
||||
{
|
||||
int key = 0x655F;
|
||||
for (int i = 0; i < chunk.Length; i++)
|
||||
{
|
||||
chunk[i] ^= (byte)key;
|
||||
key *= 0x4115;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class Deserializer
|
||||
{
|
||||
byte[] m_chunk;
|
||||
|
||||
public List<TableRow> DeserializeUTFChunk (byte[] chunk)
|
||||
{
|
||||
m_chunk = chunk;
|
||||
if (!Binary.AsciiEqual (m_chunk, 0, "@UTF"))
|
||||
throw new InvalidFormatException();
|
||||
var chunk_length = BigEndian.ToInt32 (m_chunk, 4);
|
||||
using (var mem = new MemoryStream (m_chunk, 8, chunk_length))
|
||||
using (var input = new BigEndianReader (mem))
|
||||
{
|
||||
int rows_offset = input.ReadInt32();
|
||||
int strings_offset = input.ReadInt32() + 8;
|
||||
int data_offset = input.ReadInt32() + 8;
|
||||
input.Skip (4);
|
||||
int column_count = input.ReadInt16();
|
||||
int row_length = input.ReadInt16();
|
||||
int row_count = input.ReadInt32();
|
||||
|
||||
var columns = new List<Column> (column_count);
|
||||
for (int i = 0; i < column_count; ++i)
|
||||
{
|
||||
byte flags = input.ReadByte();
|
||||
if (0 == flags)
|
||||
{
|
||||
input.Skip (3);
|
||||
flags = input.ReadByte();
|
||||
}
|
||||
int name_offset = strings_offset + input.ReadInt32();
|
||||
var column = new Column
|
||||
{
|
||||
Flags = (TableFlags)flags,
|
||||
Name = ReadString (name_offset),
|
||||
};
|
||||
columns.Add (column);
|
||||
}
|
||||
|
||||
var table = new List<TableRow> (row_count);
|
||||
int next_offset = rows_offset;
|
||||
for (int i = 0; i < row_count; ++i)
|
||||
{
|
||||
input.Position = next_offset;
|
||||
next_offset += row_length;
|
||||
var row = new TableRow (column_count);
|
||||
table.Add (row);
|
||||
foreach (var column in columns)
|
||||
{
|
||||
var storage = column.Flags & TableFlags.StorageMask;
|
||||
if (TableFlags.StorageNone == storage
|
||||
|| TableFlags.StorageZero == storage
|
||||
|| TableFlags.StorageConstant == storage)
|
||||
continue;
|
||||
switch (column.Flags & TableFlags.TypeMask)
|
||||
{
|
||||
case TableFlags.TypeByte:
|
||||
row[column.Name] = (int)input.ReadByte();
|
||||
break;
|
||||
|
||||
case TableFlags.TypeSByte:
|
||||
row[column.Name] = (int)input.ReadSByte();
|
||||
break;
|
||||
|
||||
case TableFlags.TypeUInt16:
|
||||
row[column.Name] = (int)input.ReadUInt16();
|
||||
break;
|
||||
|
||||
case TableFlags.TypeInt16:
|
||||
row[column.Name] = (int)input.ReadInt16();
|
||||
break;
|
||||
|
||||
case TableFlags.TypeUInt32:
|
||||
case TableFlags.TypeInt32:
|
||||
row[column.Name] = input.ReadInt32();
|
||||
break;
|
||||
|
||||
case TableFlags.TypeUInt64:
|
||||
case TableFlags.TypeInt64:
|
||||
row[column.Name] = input.ReadInt64();
|
||||
break;
|
||||
|
||||
case TableFlags.TypeFloat32:
|
||||
row[column.Name] = input.ReadSingle();
|
||||
break;
|
||||
|
||||
case TableFlags.TypeString:
|
||||
{
|
||||
int offset = strings_offset + input.ReadInt32();
|
||||
row[column.Name] = ReadString (offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case TableFlags.TypeData:
|
||||
{
|
||||
int offset = data_offset + input.ReadInt32();
|
||||
int length = input.ReadInt32();
|
||||
row[column.Name] = m_chunk.Skip (offset).Take (length).ToArray();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
string ReadString (int offset)
|
||||
{
|
||||
return Binary.GetCString (m_chunk, offset, 0xFF, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Column
|
||||
{
|
||||
public TableFlags Flags;
|
||||
public string Name;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum TableFlags : byte
|
||||
{
|
||||
StorageMask = 0xF0,
|
||||
StorageNone = 0x00,
|
||||
StorageZero = 0x10,
|
||||
StorageConstant = 0x30,
|
||||
|
||||
TypeMask = 0x0F,
|
||||
TypeByte = 0x00,
|
||||
TypeSByte = 0x01,
|
||||
TypeUInt16 = 0x02,
|
||||
TypeInt16 = 0x03,
|
||||
TypeUInt32 = 0x04,
|
||||
TypeInt32 = 0x05,
|
||||
TypeUInt64 = 0x06,
|
||||
TypeInt64 = 0x07,
|
||||
TypeFloat32 = 0x08,
|
||||
TypeFloat64 = 0x09,
|
||||
TypeString = 0x0A,
|
||||
TypeData = 0x0B,
|
||||
}
|
||||
}
|
1011
ArcFormats/Cri/AudioHCA.cs
Normal file
1011
ArcFormats/Cri/AudioHCA.cs
Normal file
File diff suppressed because it is too large
Load Diff
125
ArcFormats/Cri/BigEndianReader.cs
Normal file
125
ArcFormats/Cri/BigEndianReader.cs
Normal file
@ -0,0 +1,125 @@
|
||||
//! \file BigEndianReader.cs
|
||||
//! \date Wed Mar 02 23:27:29 2016
|
||||
//! \brief Wrapper around BinaryReader that reads data in a big endian order.
|
||||
//
|
||||
// Copyright (C) 2016 by morkt
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Cri
|
||||
{
|
||||
public class BigEndianReader : IDisposable
|
||||
{
|
||||
BinaryReader m_input;
|
||||
byte[] m_buffer = new byte[8];
|
||||
|
||||
public long Position
|
||||
{
|
||||
get { return m_input.BaseStream.Position; }
|
||||
set { m_input.BaseStream.Position = value; }
|
||||
}
|
||||
|
||||
public BigEndianReader(Stream input)
|
||||
{
|
||||
m_input = new BinaryReader (input, Encoding.UTF8, false);
|
||||
}
|
||||
|
||||
public BigEndianReader (Stream input, Encoding enc, bool leave_open = false)
|
||||
{
|
||||
m_input = new BinaryReader (input, enc, leave_open);
|
||||
}
|
||||
|
||||
public int Read (byte[] buffer, int index, int count)
|
||||
{
|
||||
return m_input.Read (buffer, index, count);
|
||||
}
|
||||
|
||||
public void Skip (int amount)
|
||||
{
|
||||
m_input.BaseStream.Seek (amount, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
public byte ReadByte ()
|
||||
{
|
||||
return m_input.ReadByte();
|
||||
}
|
||||
|
||||
public sbyte ReadSByte ()
|
||||
{
|
||||
return m_input.ReadSByte();
|
||||
}
|
||||
|
||||
public short ReadInt16 ()
|
||||
{
|
||||
return Binary.BigEndian (m_input.ReadInt16());
|
||||
}
|
||||
|
||||
public ushort ReadUInt16 ()
|
||||
{
|
||||
return Binary.BigEndian (m_input.ReadUInt16());
|
||||
}
|
||||
|
||||
public int ReadInt32 ()
|
||||
{
|
||||
return Binary.BigEndian (m_input.ReadInt32());
|
||||
}
|
||||
|
||||
public uint ReadUInt32 ()
|
||||
{
|
||||
return Binary.BigEndian (m_input.ReadUInt32());
|
||||
}
|
||||
|
||||
public long ReadInt64 ()
|
||||
{
|
||||
return Binary.BigEndian (m_input.ReadInt64());
|
||||
}
|
||||
|
||||
public ulong ReadUInt64 ()
|
||||
{
|
||||
return Binary.BigEndian (m_input.ReadUInt64());
|
||||
}
|
||||
|
||||
public float ReadSingle ()
|
||||
{
|
||||
if (4 != m_input.Read (m_buffer, 0, 4))
|
||||
throw new EndOfStreamException();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse (m_buffer, 0, 4);
|
||||
return BitConverter.ToSingle (m_buffer, 0);
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
bool _disposed = false;
|
||||
public void Dispose ()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
m_input.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
329
ArcFormats/Cri/ImageXTX.cs
Normal file
329
ArcFormats/Cri/ImageXTX.cs
Normal file
@ -0,0 +1,329 @@
|
||||
//! \file ImageXTX.cs
|
||||
//! \date Mon Feb 29 19:14:55 2016
|
||||
//! \brief Xbox 360 texture.
|
||||
//
|
||||
// Copyright (C) 2016 by morkt
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Windows.Media;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Cri
|
||||
{
|
||||
internal class XtxMetaData : ImageMetaData
|
||||
{
|
||||
public byte Format;
|
||||
public uint DataOffset;
|
||||
public int AlignedWidth;
|
||||
public int AlignedHeight;
|
||||
}
|
||||
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class XtxFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "XTX"; } }
|
||||
public override string Description { get { return "Xbox 360 texture format"; } }
|
||||
public override uint Signature { get { return 0x00787478; } } // 'xtx'
|
||||
|
||||
public XtxFormat ()
|
||||
{
|
||||
Signatures = new uint[] { 0x00787478, 0 };
|
||||
}
|
||||
|
||||
public override ImageMetaData ReadMetaData (Stream stream)
|
||||
{
|
||||
var header = new byte[0x20];
|
||||
if (0x20 != stream.Read (header, 0, 0x20))
|
||||
return null;
|
||||
if (!Binary.AsciiEqual (header, 0, "xtx\0"))
|
||||
{
|
||||
var header_size = LittleEndian.ToUInt32 (header, 0);
|
||||
if (header_size >= stream.Length)
|
||||
return null;
|
||||
stream.Position = header_size;
|
||||
if (0x20 != stream.Read (header, 0, 0x20))
|
||||
return null;
|
||||
if (!Binary.AsciiEqual (header, 0, "xtx\0"))
|
||||
return null;
|
||||
}
|
||||
if (header[4] > 2)
|
||||
return null;
|
||||
int aligned_width = BigEndian.ToInt32 (header, 8);
|
||||
int aligned_height = BigEndian.ToInt32 (header, 0xC);
|
||||
if (aligned_width <= 0 || aligned_height <= 0)
|
||||
return null;
|
||||
return new XtxMetaData
|
||||
{
|
||||
Width = BigEndian.ToUInt32 (header, 0x10),
|
||||
Height = BigEndian.ToUInt32 (header, 0x14),
|
||||
OffsetX = BigEndian.ToInt32 (header, 0x18),
|
||||
OffsetY = BigEndian.ToInt32 (header, 0x1C),
|
||||
BPP = 32,
|
||||
Format = header[4],
|
||||
AlignedWidth = aligned_width,
|
||||
AlignedHeight = aligned_height,
|
||||
DataOffset = (uint)stream.Position,
|
||||
};
|
||||
}
|
||||
|
||||
public override ImageData Read (Stream stream, ImageMetaData info)
|
||||
{
|
||||
var reader = new XtxReader (stream, (XtxMetaData)info);
|
||||
var pixels = reader.Unpack();
|
||||
return ImageData.Create (info, reader.Format, null, pixels);
|
||||
}
|
||||
|
||||
public override void Write (Stream file, ImageData image)
|
||||
{
|
||||
throw new System.NotImplementedException ("XtxFormat.Write not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class XtxReader
|
||||
{
|
||||
Stream m_input;
|
||||
int m_width;
|
||||
int m_height;
|
||||
XtxMetaData m_info;
|
||||
byte[] m_output;
|
||||
int m_output_stride;
|
||||
|
||||
public byte[] Data { get { return m_output; } }
|
||||
public PixelFormat Format { get; private set; }
|
||||
|
||||
public XtxReader (Stream input, XtxMetaData info)
|
||||
{
|
||||
m_input = input;
|
||||
m_info = info;
|
||||
m_input.Position = info.DataOffset;
|
||||
m_width = (int)m_info.Width;
|
||||
m_output_stride = m_width * 4;
|
||||
m_height = (int)m_info.Height;
|
||||
m_output = new byte[m_output_stride*m_height];
|
||||
}
|
||||
|
||||
public byte[] Unpack ()
|
||||
{
|
||||
Format = PixelFormats.Bgra32;
|
||||
if (0 == m_info.Format)
|
||||
ReadTex0();
|
||||
else if (1 == m_info.Format)
|
||||
ReadTex1();
|
||||
else
|
||||
ReadTex2();
|
||||
return m_output;
|
||||
}
|
||||
|
||||
void ReadTex0 ()
|
||||
{
|
||||
int total = m_info.AlignedWidth * m_info.AlignedHeight;
|
||||
var texture = new byte[total*4];
|
||||
m_input.Read (texture, 0, texture.Length);
|
||||
int src = 0;
|
||||
for (int i = 0; i < total; ++i)
|
||||
{
|
||||
int y = GetY (i, m_info.AlignedWidth, 4);
|
||||
int x = GetX (i, m_info.AlignedWidth, 4);
|
||||
if (y < m_height && x < m_width)
|
||||
{
|
||||
int dst = m_output_stride * y + x * 4;
|
||||
m_output[dst] = texture[src+3];
|
||||
m_output[dst+1] = texture[src+2];
|
||||
m_output[dst+2] = texture[src+1];
|
||||
m_output[dst+3] = texture[src];
|
||||
}
|
||||
src += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadTex1 ()
|
||||
{
|
||||
int total = m_info.AlignedWidth * m_info.AlignedHeight;
|
||||
var texture = new byte[total*2];
|
||||
var packed = new byte[total*2];
|
||||
m_input.Read (texture, 0, texture.Length);
|
||||
int stride = m_info.AlignedWidth;
|
||||
int src = 0;
|
||||
for (int i = 0; i < total; ++i)
|
||||
{
|
||||
int y = GetY (i, m_info.AlignedWidth, 2);
|
||||
int x = GetX (i, m_info.AlignedWidth, 2);
|
||||
int dst = (x + y * stride) * 2;
|
||||
packed[dst] = texture[src+1];
|
||||
packed[dst+1] = texture[src];
|
||||
src += 2;
|
||||
}
|
||||
Format = PixelFormats.Bgr565;
|
||||
m_output = packed;
|
||||
throw new NotImplementedException ("XTX textures format 1 not implemented");
|
||||
}
|
||||
|
||||
void ReadTex2 ()
|
||||
{
|
||||
int tex_width = m_info.AlignedWidth >> 2;
|
||||
int total = tex_width * (m_info.AlignedHeight >> 2);
|
||||
var texture = new byte[m_info.AlignedWidth * m_info.AlignedHeight];
|
||||
var packed = new byte[m_info.AlignedWidth * m_info.AlignedHeight];
|
||||
m_input.Read (texture, 0, texture.Length);
|
||||
int src = 0;
|
||||
for (int i = 0; i < total; ++i)
|
||||
{
|
||||
int y = GetY (i, tex_width, 0x10);
|
||||
int x = GetX (i, tex_width, 0x10);
|
||||
int dst = (x + y * tex_width) * 16;
|
||||
for (int j = 0; j < 8; ++j)
|
||||
{
|
||||
packed[dst++] = texture[src+1];
|
||||
packed[dst++] = texture[src];
|
||||
src += 2;
|
||||
}
|
||||
}
|
||||
UnpackDXT5 (packed);
|
||||
}
|
||||
|
||||
static int GetY (int i, int width, byte level)
|
||||
{
|
||||
int v1 = (level >> 2) + (level >> 1 >> (level >> 2));
|
||||
int v2 = i << v1;
|
||||
int v3 = (v2 & 0x3F) + ((v2 >> 2) & 0x1C0) + ((v2 >> 3) & 0x1FFFFE00);
|
||||
return ((v3 >> 4) & 1)
|
||||
+ ((((v3 & ((level << 6) - 1) & -0x20)
|
||||
+ ((((v2 & 0x3F) + ((v2 >> 2) & 0xC0)) & 0xF) << 1)) >> (v1 + 3)) & -2)
|
||||
+ ((((v2 >> 10) & 2) + ((v3 >> (v1 + 6)) & 1)
|
||||
+ (((v3 >> (v1 + 7)) / ((width + 31) >> 5)) << 2)) << 3);
|
||||
}
|
||||
|
||||
static int GetX (int i, int width, byte level)
|
||||
{
|
||||
int v1 = (level >> 2) + (level >> 1 >> (level >> 2));
|
||||
int v2 = i << v1;
|
||||
int v3 = (v2 & 0x3F) + ((v2 >> 2) & 0x1C0) + ((v2 >> 3) & 0x1FFFFE00);
|
||||
return ((((level << 3) - 1) & ((v3 >> 1) ^ (v3 ^ (v3 >> 1)) & 0xF)) >> v1)
|
||||
+ ((((((v2 >> 6) & 0xFF) + ((v3 >> (v1 + 5)) & 0xFE)) & 3)
|
||||
+ (((v3 >> (v1 + 7)) % (((width + 31)) >> 5)) << 2)) << 3);
|
||||
}
|
||||
|
||||
void UnpackDXT5 (byte[] input)
|
||||
{
|
||||
int src = 0;
|
||||
for (int y = 0; y < m_info.AlignedHeight; y += 4)
|
||||
for (int x = 0; x < m_info.AlignedWidth; x += 4)
|
||||
{
|
||||
DecompressDXT5Block (input, src, y, x);
|
||||
src += 16;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] m_dxt5_alpha = new byte[16];
|
||||
|
||||
void DecompressDXT5Block (byte[] input, int src, int block_y, int block_x)
|
||||
{
|
||||
byte alpha0 = input[src];
|
||||
byte alpha1 = input[src+1];
|
||||
|
||||
DecompressDXT5Alpha (input, src+2, m_dxt5_alpha);
|
||||
|
||||
ushort color0 = LittleEndian.ToUInt16 (input, src+8);
|
||||
ushort color1 = LittleEndian.ToUInt16 (input, src+10);
|
||||
|
||||
int t = (color0 >> 11) * 255 + 16;
|
||||
byte r0 = (byte)((t / 32 + t) / 32);
|
||||
t = ((color0 & 0x07E0) >> 5) * 255 + 32;
|
||||
byte g0 = (byte)((t / 64 + t) / 64);
|
||||
t = (color0 & 0x001F) * 255 + 16;
|
||||
byte b0 = (byte)((t / 32 + t) / 32);
|
||||
|
||||
t = (color1 >> 11) * 255 + 16;
|
||||
byte r1 = (byte)((t / 32 + t) / 32);
|
||||
t = ((color1 & 0x07E0) >> 5) * 255 + 32;
|
||||
byte g1 = (byte)((t / 64 + t) / 64);
|
||||
t = (color1 & 0x001F) * 255 + 16;
|
||||
byte b1 = (byte)((t / 32 + t) / 32);
|
||||
|
||||
uint code = LittleEndian.ToUInt32 (input, src+12);
|
||||
|
||||
for (int y = 0; y < 4 && (block_y + y) < m_height; ++y)
|
||||
for (int x = 0; x < 4 && (block_x + x) < m_width; ++x)
|
||||
{
|
||||
int alpha_code = m_dxt5_alpha[4 * y + x];
|
||||
byte alpha;
|
||||
if (0 == alpha_code)
|
||||
alpha = alpha0;
|
||||
else if (1 == alpha_code)
|
||||
alpha = alpha1;
|
||||
else if (alpha0 > alpha1)
|
||||
alpha = (byte)(((8 - alpha_code) * alpha0 + (alpha_code - 1) * alpha1) / 7);
|
||||
else if (6 == alpha_code)
|
||||
alpha = 0;
|
||||
else if (7 == alpha_code)
|
||||
alpha = 0xFF;
|
||||
else
|
||||
alpha = (byte)(((6 - alpha_code) * alpha0 + (alpha_code - 1) * alpha1) / 5);
|
||||
|
||||
int dst = m_output_stride * (block_y + y) + (block_x + x) * 4;
|
||||
switch (code & 3)
|
||||
{
|
||||
case 0:
|
||||
PutPixel (dst, r0, g0, b0, alpha);
|
||||
break;
|
||||
case 1:
|
||||
PutPixel (dst, r1, g1, b1, alpha);
|
||||
break;
|
||||
case 2:
|
||||
PutPixel (dst, (byte)((2 * r0 + r1) / 3), (byte)((2 * g0 + g1) / 3), (byte)((2 * b0 + b1) / 3), alpha);
|
||||
break;
|
||||
case 3:
|
||||
PutPixel (dst, (byte)((r0 + 2 * r1) / 3), (byte)((g0 + 2 * g1) / 3), (byte)((b0 + 2 * b1) / 3), alpha);
|
||||
break;
|
||||
}
|
||||
code >>= 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void DecompressDXT5Alpha (byte[] input, int src, byte[] output)
|
||||
{
|
||||
int dst = 0;
|
||||
for (int j = 0; j < 2; ++j)
|
||||
{
|
||||
int block = input[src++];
|
||||
block |= input[src++] << 8;
|
||||
block |= input[src++] << 16;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
output[dst++] = (byte)(block & 7);
|
||||
block >>= 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PutPixel (int dst, byte r, byte g, byte b, byte a)
|
||||
{
|
||||
m_output[dst] = b;
|
||||
m_output[dst+1] = g;
|
||||
m_output[dst+2] = r;
|
||||
m_output[dst+3] = a;
|
||||
}
|
||||
}
|
||||
}
|
@ -197,6 +197,7 @@ Hime to Majin to Koi Suru Tamashii<br/>
|
||||
Imouto Style<br/>
|
||||
Inaho no Mirai</br>
|
||||
Mayoeru Futari to Sekai no Subete<br/>
|
||||
Mahoutsukai no Yoru<br/>
|
||||
Natsupochi<br/>
|
||||
Nidaime wa ☆ Mahou Shoujo<br/>
|
||||
Nuki Doki!<br/>
|
||||
@ -338,7 +339,10 @@ Tsurugi Otome Noah<br/>
|
||||
<tr><td>*.cpb</td><td><tt>CPB\x1a</tt><br><tt>TYP1</tt></td><td>No</td></tr>
|
||||
<tr class="odd"><td>*.mfg<br/>*.mfm<br/>*.mfs</td><td><tt>ALPF</tt></td><td>No</td><td rowspan="2">Silky's</td><td rowspan="2">Jokei Kazoku</td></tr>
|
||||
<tr class="odd"><td>*</td><td><tt>MFG_</tt><br/><tt>MFGA</tt><br/><tt>MFGC</tt></td><td>No</td></tr>
|
||||
<tr><td>*.pmp<br/>*.pmw</td><td>-</td><td>Yes</td><td>ScenePlayer</td><td>Nyuujoku Hitozuma Jogakuen</td></tr>
|
||||
<tr><td>*.pmp<br/>*.pmw</td><td>-</td><td>Yes</td><td>ScenePlayer</td><td>
|
||||
Eraburu ~Erabu + Love x Double de~<br/>
|
||||
Nyuujoku Hitozuma Jogakuen<br/>
|
||||
</td></tr>
|
||||
<tr class="odd"><td>*.dat</td><td><tt>GAMEDAT PACK</tt><br/><tt>GAMEDAT PAC2</tt></td><td>No</td><td rowspan="2"> bootUP!<br/>Pajamas Soft<br/>Aries</td><td rowspan="2">
|
||||
Aneimo 2 ~Second Stage~<br/>
|
||||
Momichupa Teacher!<br/>
|
||||
@ -386,6 +390,7 @@ Yatohime Zankikou<br/>
|
||||
<tr class="odd"><td>*.eri<br/>*.mio</td><td><tt>Entis\x1a</tt></td><td>No</td></tr>
|
||||
<tr><td>*.saf</td><td>-</td><td>No</td><td>Lune</td><td>Rinkan Gakuen</td></tr>
|
||||
<tr class="odd"><td>*.arc+*.ari</td><td><tt>WFL1</tt></td><td>No</td><td rowspan="2">KaGuYa</td><td rowspan="2">
|
||||
Dungeon Crusaderz ~Tales of Demon Eater~<br/>
|
||||
Dungeon Crusaderz 2 ~Eigou no Rakudo~<br/>
|
||||
Onna Kyoushi<br/>
|
||||
Magical Witch Academy<br/>
|
||||
@ -671,6 +676,7 @@ MILK Junkies<br/>
|
||||
<tr class="odd"><td>*.zbm</td><td><tt>amp_</tt></td><td>No</td></tr>
|
||||
<tr><td>*.vfs</td><td><tt>VF</tt></td><td>No</td><td rowspan="4">Aoi</td><td rowspan="4">
|
||||
Dancing Crazies<br/>
|
||||
Level Justice<br/>
|
||||
</td></tr>
|
||||
<tr><td>*.iph</td><td><tt>RIFF....IPH fmt</tt></td><td>No</td></tr>
|
||||
<tr><td>*.aog</td><td><tt>AoiOgg</tt></td><td>No</td></tr>
|
||||
@ -711,6 +717,11 @@ Tokyo Necro<br/>
|
||||
Thanatos no Koi ~In Ane Otouto Soukan~<br/>
|
||||
</td></tr>
|
||||
<tr class="odd"><td>*.gps</td><td><tt>GPS</tt></td><td>No</td></tr>
|
||||
<tr><td>*.cpk</td><td><tt>CPK</tt></td><td>No</td><td rowspan="3">CRI</td><td rowspan="3">
|
||||
Iwaihime<br/>
|
||||
</td></tr>
|
||||
<tr><td>*.xtx</td><td><tt>xtx</tt></td><td>No</td></tr>
|
||||
<tr><td>*.hca</td><td><tt>HCA</tt></td><td>No</td></tr>
|
||||
</table>
|
||||
<p><a name="note-1" class="footnote">1</a> Non-encrypted only</p>
|
||||
</body>
|
||||
|
Loading…
Reference in New Issue
Block a user