2017-06-21 01:19:47 +03:00

757 lines
29 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Reflection;
using SonicAudioLib.IO;
using SonicAudioLib.CriMw;
namespace SonicAudioLib.Archives
{
public class CriCpkEntry : EntryBase
{
private DateTime updateDateTime;
public string DirectoryName { get; set; }
public string Name { get; set; }
public uint Id { get; set; }
public string Comment { get; set; }
public bool IsCompressed { get; set; }
public long UncompressedLength { get; set; }
public DateTime UpdateDateTime
{
get
{
if (FilePath != null)
{
return FilePath.LastWriteTime;
}
return updateDateTime;
}
set
{
updateDateTime = value;
}
}
}
public enum CriCpkMode
{
None = -1,
FileNameIdAndGroup = 5,
IdAndGroup = 4,
FileNameAndGroup = 3,
FileNameAndId = 2,
FileName = 1,
Id = 0,
}
public class CriCpkArchive : ArchiveBase<CriCpkEntry>
{
private ushort align = 1;
private CriCpkMode mode = CriCpkMode.FileName;
private bool enableMask = false;
public ushort Align
{
get
{
return align;
}
set
{
if (align != 1)
{
throw new NotImplementedException("Alignment is currently not implemented.");
}
align = value;
}
}
public CriCpkMode Mode
{
get
{
return mode;
}
set
{
mode = value;
}
}
public bool EnableMask
{
get
{
return enableMask;
}
set
{
enableMask = value;
}
}
public string Comment { get; set; }
public override void Read(Stream source)
{
using (CriTableReader reader = CriCpkSection.Open(source, source.Position, "CPK "))
{
reader.Read();
bool isLatestVersion = reader.ContainsField("CpkMode");
if (isLatestVersion)
{
mode = (CriCpkMode)reader.GetUInt32("CpkMode");
}
else
{
bool tocEnabled = reader.GetUInt64("TocOffset") > 0;
bool itocEnabled = reader.GetUInt64("ItocOffset") > 0;
if (tocEnabled && !itocEnabled)
{
mode = CriCpkMode.FileName;
}
else if (!tocEnabled && itocEnabled)
{
mode = CriCpkMode.Id;
}
else if (tocEnabled && itocEnabled)
{
mode = CriCpkMode.FileNameAndId;
}
else
{
mode = CriCpkMode.None;
}
}
// No need to waste time, stop right there.
if (mode == CriCpkMode.None)
{
return;
}
long tocPosition = (long)reader.GetUInt64("TocOffset");
long itocPosition = (long)reader.GetUInt64("ItocOffset");
long etocPosition = (long)reader.GetUInt64("EtocOffset");
long contentPosition = (long)reader.GetUInt64("ContentOffset");
align = reader.GetUInt16("Align");
if (mode == CriCpkMode.FileName || mode == CriCpkMode.FileNameAndId)
{
using (CriTableReader tocReader = CriCpkSection.Open(source, tocPosition, "TOC "))
using (CriTableReader etocReader = CriCpkSection.Open(source, etocPosition, "ETOC"))
{
while (tocReader.Read())
{
CriCpkEntry entry = new CriCpkEntry();
entry.DirectoryName = tocReader.GetString("DirName");
entry.Name = tocReader.GetString("FileName");
entry.Length = tocReader.GetUInt32("FileSize");
entry.Position = (long)tocReader.GetUInt64("FileOffset");
entry.Id = isLatestVersion ? tocReader.GetUInt32("ID") : tocReader.GetUInt32("Info");
entry.Comment = tocReader.GetString("UserString");
entry.UncompressedLength = tocReader.GetUInt32("ExtractSize");
entry.IsCompressed = entry.Length != entry.UncompressedLength;
if (contentPosition < tocPosition)
{
entry.Position += contentPosition;
}
else
{
entry.Position += tocPosition;
}
entry.Position = Helpers.Align(entry.Position, align);
etocReader.MoveToRow(tocReader.CurrentRow);
entry.UpdateDateTime = DateTimeFromCpkDateTime(etocReader.GetUInt64("UpdateDateTime"));
entries.Add(entry);
}
}
if (mode == CriCpkMode.FileNameAndId && isLatestVersion)
{
using (CriTableReader itocReader = CriCpkSection.Open(source, itocPosition, "ITOC"))
{
while (itocReader.Read())
{
entries[itocReader.GetInt32("TocIndex")].Id = (uint)itocReader.GetInt32("ID");
}
}
}
}
else if (mode == CriCpkMode.Id)
{
using (CriTableReader itocReader = CriCpkSection.Open(source, itocPosition, "ITOC"))
{
while (itocReader.Read())
{
if (itocReader.GetUInt32("FilesL") > 0)
{
using (CriTableReader dataReader = itocReader.GetTableReader("DataL"))
{
while (dataReader.Read())
{
CriCpkEntry entry = new CriCpkEntry();
entry.Id = dataReader.GetUInt16("ID");
entry.Length = dataReader.GetUInt16("FileSize");
entry.UncompressedLength = dataReader.GetUInt16("ExtractSize");
entry.IsCompressed = entry.Length != entry.UncompressedLength;
entries.Add(entry);
}
}
}
if (itocReader.GetUInt32("FilesH") > 0)
{
using (CriTableReader dataReader = itocReader.GetTableReader("DataH"))
{
while (dataReader.Read())
{
CriCpkEntry entry = new CriCpkEntry();
entry.Id = dataReader.GetUInt16("ID");
entry.Length = dataReader.GetUInt32("FileSize");
entry.UncompressedLength = dataReader.GetUInt32("ExtractSize");
entry.IsCompressed = entry.Length != entry.UncompressedLength;
entries.Add(entry);
}
}
}
}
}
long entryPosition = contentPosition;
foreach (CriCpkEntry entry in entries.OrderBy(entry => entry.Id))
{
entryPosition = Helpers.Align(entryPosition, align);
entry.Position = entryPosition;
entryPosition += entry.Length;
}
}
else
{
throw new NotImplementedException($"Unimplemented CPK mode ({mode})");
}
Comment = reader.GetString("Comment");
}
}
public override void Write(Stream destination)
{
string GetToolVersion()
{
AssemblyName assemblyName = Assembly.GetEntryAssembly().GetName();
return $"{assemblyName.Name}, {assemblyName.Version.ToString()}";
}
DataPool vldPool = new DataPool(Align, 2048);
vldPool.ProgressChanged += OnProgressChanged;
using (CriCpkSection cpkSection = new CriCpkSection(destination, "CPK ", enableMask))
{
cpkSection.Writer.WriteStartTable("CpkHeader");
cpkSection.Writer.WriteField("UpdateDateTime", typeof(ulong));
cpkSection.Writer.WriteField("FileSize", typeof(ulong));
cpkSection.Writer.WriteField("ContentOffset", typeof(ulong));
cpkSection.Writer.WriteField("ContentSize", typeof(ulong));
if (mode == CriCpkMode.FileName || mode == CriCpkMode.FileNameAndId)
{
cpkSection.Writer.WriteField("TocOffset", typeof(ulong));
cpkSection.Writer.WriteField("TocSize", typeof(ulong));
cpkSection.Writer.WriteField("TocCrc", typeof(uint), null);
cpkSection.Writer.WriteField("EtocOffset", typeof(ulong));
cpkSection.Writer.WriteField("EtocSize", typeof(ulong));
}
else
{
cpkSection.Writer.WriteField("TocOffset", typeof(ulong), null);
cpkSection.Writer.WriteField("TocSize", typeof(ulong), null);
cpkSection.Writer.WriteField("TocCrc", typeof(uint), null);
cpkSection.Writer.WriteField("EtocOffset", typeof(ulong), null);
cpkSection.Writer.WriteField("EtocSize", typeof(ulong), null);
}
if (mode == CriCpkMode.Id || mode == CriCpkMode.FileNameAndId)
{
cpkSection.Writer.WriteField("ItocOffset", typeof(ulong));
cpkSection.Writer.WriteField("ItocSize", typeof(ulong));
cpkSection.Writer.WriteField("ItocCrc", typeof(uint), null);
}
else
{
cpkSection.Writer.WriteField("ItocOffset", typeof(ulong), null);
cpkSection.Writer.WriteField("ItocSize", typeof(ulong), null);
cpkSection.Writer.WriteField("ItocCrc", typeof(uint), null);
}
cpkSection.Writer.WriteField("GtocOffset", typeof(ulong), null);
cpkSection.Writer.WriteField("GtocSize", typeof(ulong), null);
cpkSection.Writer.WriteField("GtocCrc", typeof(uint), null);
cpkSection.Writer.WriteField("EnabledPackedSize", typeof(ulong));
cpkSection.Writer.WriteField("EnabledDataSize", typeof(ulong));
cpkSection.Writer.WriteField("TotalDataSize", typeof(ulong), null);
cpkSection.Writer.WriteField("Tocs", typeof(uint), null);
cpkSection.Writer.WriteField("Files", typeof(uint));
cpkSection.Writer.WriteField("Groups", typeof(uint));
cpkSection.Writer.WriteField("Attrs", typeof(uint));
cpkSection.Writer.WriteField("TotalFiles", typeof(uint), null);
cpkSection.Writer.WriteField("Directories", typeof(uint), null);
cpkSection.Writer.WriteField("Updates", typeof(uint), null);
cpkSection.Writer.WriteField("Version", typeof(ushort));
cpkSection.Writer.WriteField("Revision", typeof(ushort));
cpkSection.Writer.WriteField("Align", typeof(ushort));
cpkSection.Writer.WriteField("Sorted", typeof(ushort));
cpkSection.Writer.WriteField("EID", typeof(ushort), null);
cpkSection.Writer.WriteField("CpkMode", typeof(uint));
cpkSection.Writer.WriteField("Tvers", typeof(string));
if (!string.IsNullOrEmpty(Comment))
{
cpkSection.Writer.WriteField("Comment", typeof(string));
}
else
{
cpkSection.Writer.WriteField("Comment", typeof(string), null);
}
cpkSection.Writer.WriteField("Codec", typeof(uint));
cpkSection.Writer.WriteField("DpkItoc", typeof(uint));
MemoryStream tocMemoryStream = null;
MemoryStream itocMemoryStream = null;
MemoryStream etocMemoryStream = null;
if (mode == CriCpkMode.FileName || mode == CriCpkMode.FileNameAndId)
{
tocMemoryStream = new MemoryStream();
etocMemoryStream = new MemoryStream();
var orderedEntries = entries.OrderBy(entry => entry.Name).ToList();
using (CriCpkSection tocSection = new CriCpkSection(tocMemoryStream, "TOC ", enableMask))
using (CriCpkSection etocSection = new CriCpkSection(etocMemoryStream, "ETOC", enableMask))
{
tocSection.Writer.WriteStartTable("CpkTocInfo");
tocSection.Writer.WriteField("DirName", typeof(string));
tocSection.Writer.WriteField("FileName", typeof(string));
tocSection.Writer.WriteField("FileSize", typeof(uint));
tocSection.Writer.WriteField("ExtractSize", typeof(uint));
tocSection.Writer.WriteField("FileOffset", typeof(ulong));
tocSection.Writer.WriteField("ID", typeof(uint));
tocSection.Writer.WriteField("UserString", typeof(string));
etocSection.Writer.WriteStartTable("CpkEtocInfo");
etocSection.Writer.WriteField("UpdateDateTime", typeof(ulong));
etocSection.Writer.WriteField("LocalDir", typeof(string));
foreach (CriCpkEntry entry in orderedEntries)
{
tocSection.Writer.WriteRow(true,
(entry.DirectoryName).Replace('\\', '/'),
entry.Name,
(uint)entry.Length,
(uint)entry.Length,
(ulong)(vldPool.Length - 2048),
entry.Id,
entry.Comment);
etocSection.Writer.WriteRow(true,
CpkDateTimeFromDateTime(entry.UpdateDateTime),
entry.DirectoryName);
vldPool.Put(entry.FilePath);
}
tocSection.Writer.WriteEndTable();
etocSection.Writer.WriteEndTable();
}
if (mode == CriCpkMode.FileNameAndId)
{
itocMemoryStream = new MemoryStream();
using (CriCpkSection itocSection = new CriCpkSection(itocMemoryStream, "ITOC", enableMask))
{
itocSection.Writer.WriteStartTable("CpkExtendId");
itocSection.Writer.WriteField("ID", typeof(int));
itocSection.Writer.WriteField("TocIndex", typeof(int));
foreach (CriCpkEntry entry in orderedEntries)
{
itocSection.Writer.WriteRow(true,
(int)entry.Id,
orderedEntries.IndexOf(entry));
}
itocSection.Writer.WriteEndTable();
}
}
}
else if (mode == CriCpkMode.Id)
{
itocMemoryStream = new MemoryStream();
using (CriCpkSection itocSection = new CriCpkSection(itocMemoryStream, "ITOC", enableMask))
{
itocSection.Writer.WriteStartTable("CpkItocInfo");
itocSection.Writer.WriteField("FilesL", typeof(uint));
itocSection.Writer.WriteField("FilesH", typeof(uint));
itocSection.Writer.WriteField("DataL", typeof(byte[]));
itocSection.Writer.WriteField("DataH", typeof(byte[]));
List<CriCpkEntry> filesL = entries.Where(entry => entry.Length < ushort.MaxValue).ToList();
List<CriCpkEntry> filesH = entries.Where(entry => entry.Length > ushort.MaxValue).ToList();
itocSection.Writer.WriteStartRow();
using (MemoryStream dataMemoryStream = new MemoryStream())
using (CriTableWriter dataWriter = CriTableWriter.Create(dataMemoryStream))
{
dataWriter.WriteStartTable("CpkItocL");
dataWriter.WriteField("ID", typeof(ushort));
dataWriter.WriteField("FileSize", typeof(ushort));
dataWriter.WriteField("ExtractSize", typeof(ushort));
foreach (CriCpkEntry entry in filesL)
{
dataWriter.WriteRow(true,
(ushort)entry.Id,
(ushort)entry.Length,
(ushort)entry.Length);
}
dataWriter.WriteEndTable();
itocSection.Writer.WriteValue("DataL", dataMemoryStream.ToArray());
}
using (MemoryStream dataMemoryStream = new MemoryStream())
using (CriTableWriter dataWriter = CriTableWriter.Create(dataMemoryStream))
{
dataWriter.WriteStartTable("CpkItocH");
dataWriter.WriteField("ID", typeof(ushort));
dataWriter.WriteField("FileSize", typeof(uint));
dataWriter.WriteField("ExtractSize", typeof(uint));
foreach (CriCpkEntry entry in filesH)
{
dataWriter.WriteRow(true,
(ushort)entry.Id,
(uint)entry.Length,
(uint)entry.Length);
}
dataWriter.WriteEndTable();
itocSection.Writer.WriteValue("DataH", dataMemoryStream.ToArray());
}
itocSection.Writer.WriteValue("FilesL", (uint)filesL.Count);
itocSection.Writer.WriteValue("FilesH", (uint)filesH.Count);
itocSection.Writer.WriteEndRow();
itocSection.Writer.WriteEndTable();
}
foreach (CriCpkEntry entry in entries.OrderBy(entry => entry.Id))
{
vldPool.Put(entry.FilePath);
}
}
else
{
throw new NotImplementedException($"Unimplemented CPK mode ({mode})");
}
cpkSection.Writer.WriteStartRow();
cpkSection.Writer.WriteValue("UpdateDateTime", CpkDateTimeFromDateTime(DateTime.Now));
cpkSection.Writer.WriteValue("ContentOffset", (ulong)2048);
cpkSection.Writer.WriteValue("ContentSize", (ulong)(vldPool.Length - 2048));
if (tocMemoryStream != null)
{
cpkSection.Writer.WriteValue("TocOffset", (ulong)vldPool.Put(tocMemoryStream));
cpkSection.Writer.WriteValue("TocSize", (ulong)tocMemoryStream.Length);
}
if (itocMemoryStream != null)
{
cpkSection.Writer.WriteValue("ItocOffset", (ulong)vldPool.Put(itocMemoryStream));
cpkSection.Writer.WriteValue("ItocSize", (ulong)itocMemoryStream.Length);
}
if (etocMemoryStream != null)
{
cpkSection.Writer.WriteValue("EtocOffset", (ulong)vldPool.Put(etocMemoryStream));
cpkSection.Writer.WriteValue("EtocSize", (ulong)etocMemoryStream.Length);
}
long totalDataSize = 0;
foreach (CriCpkEntry entry in entries)
{
totalDataSize += entry.Length;
}
cpkSection.Writer.WriteValue("EnabledPackedSize", totalDataSize);
cpkSection.Writer.WriteValue("EnabledDataSize", totalDataSize);
cpkSection.Writer.WriteValue("Files", (uint)entries.Count);
cpkSection.Writer.WriteValue("Version", (ushort)7);
cpkSection.Writer.WriteValue("Revision", (ushort)2);
cpkSection.Writer.WriteValue("Align", Align);
cpkSection.Writer.WriteValue("Sorted", (ushort)1);
cpkSection.Writer.WriteValue("CpkMode", (uint)mode);
cpkSection.Writer.WriteValue("Tvers", GetToolVersion());
cpkSection.Writer.WriteValue("Comment", Comment);
cpkSection.Writer.WriteValue("FileSize", (ulong)vldPool.Length);
cpkSection.Writer.WriteEndRow();
cpkSection.Writer.WriteEndTable();
}
DataStream.Pad(destination, 2042);
DataStream.WriteCString(destination, "(c)CRI", 6);
vldPool.Write(destination);
}
public CriCpkEntry GetById(uint id)
{
return entries.FirstOrDefault(entry => (entry.Id == id));
}
public CriCpkEntry GetByName(string name)
{
return entries.FirstOrDefault(entry => (entry.Name == name));
}
public CriCpkEntry GetByPath(string path)
{
string correctedPath = path.Replace('\\', '/');
return entries.FirstOrDefault(entry =>
{
string search = string.Empty;
if (!string.IsNullOrEmpty(entry.DirectoryName))
{
search += $"{entry.DirectoryName.Replace('\\', '/')}/";
}
search += entry.Name;
return search == correctedPath;
});
}
public static void Decompress(byte[] source, byte[] destination)
{
if (Encoding.ASCII.GetString(source, 0, 8) != "CRILAYLA")
{
throw new InvalidDataException("'CRILAYLA' signature could not be found.");
}
int uncompressedLength = BitConverter.ToInt32(source, 8);
int compressedLength = BitConverter.ToInt32(source, 12);
Array.Copy(source, 16 + compressedLength, destination, 0, 256);
int srcEnd = 16 + compressedLength - 1;
int dstEnd = 256 + uncompressedLength - 1;
int dstOut = 0;
int bitData = 0;
int bitNum = 0;
int ReadBits(int count)
{
if (bitNum < count)
{
int byteNum = ((24 - bitNum) >> 3) + 1;
bitNum += byteNum * 8;
while (byteNum > 0)
{
bitData = source[srcEnd--] | bitData << 8;
byteNum--;
}
}
bitNum -= count;
return (bitData >> bitNum) & ((1 << count) - 1);
}
while (dstOut < uncompressedLength)
{
if (ReadBits(1) != 0)
{
int position = dstEnd - dstOut + ReadBits(13) + 3;
int length = 3 + ReadBits(2);
if (length == 3 + 3)
{
length += ReadBits(3);
if (length == 3 + 3 + 7)
{
length += ReadBits(5);
if (length == 3 + 3 + 7 + 31)
{
int bits;
do
{
bits = ReadBits(8);
length += bits;
} while (bits == 255);
}
}
}
dstOut += length;
Array.Copy(destination, position - length + 1, destination, dstEnd - dstOut + 1, length);
}
else
{
destination[dstEnd - dstOut++] = (byte)ReadBits(8);
}
}
}
private DateTime DateTimeFromCpkDateTime(ulong dateTime)
{
return dateTime > 0 ?
new DateTime(
(int)(dateTime >> 48), // Year (ushort)
(int)(dateTime >> 40) & 0xFF, // Month (byte)
(int)(dateTime >> 32) & 0xFF, // Day (byte)
(int)(dateTime >> 24) & 0xFF, // Hour (byte)
(int)(dateTime >> 16) & 0xFF, // Minute (byte)
(int)(dateTime >> 8) & 0xFF) // Second (byte)
: new DateTime();
}
private ulong CpkDateTimeFromDateTime(DateTime dateTime)
{
return
(ulong)dateTime.Year << 48 |
(ulong)dateTime.Month << 40 |
(ulong)dateTime.Day << 32 |
(ulong)dateTime.Hour << 24 |
(ulong)dateTime.Minute << 16 |
(ulong)dateTime.Second << 8;
}
private class CriCpkSection : IDisposable
{
private Stream destination;
private long headerPosition;
private CriTableWriter writer;
public CriTableWriter Writer
{
get
{
return writer;
}
}
public void Dispose()
{
writer.Dispose();
long position = destination.Position;
uint length = (uint)(position - (headerPosition) - 16);
destination.Seek(headerPosition + 8, SeekOrigin.Begin);
DataStream.WriteUInt32(destination, length);
destination.Seek(position, SeekOrigin.Begin);
}
public static CriTableReader Open(Stream source, long position, string expectedSignature)
{
source.Seek(position, SeekOrigin.Begin);
if (DataStream.ReadCString(source, 4) != expectedSignature)
{
throw new InvalidDataException($"'{expectedSignature}' signature could not be found.");
}
uint flag = DataStream.ReadUInt32(source);
uint tableLength = DataStream.ReadUInt32(source);
uint unknown = DataStream.ReadUInt32(source);
return CriTableReader.Create(new SubStream(source, source.Position, tableLength));
}
public CriCpkSection(Stream destination, string signature, bool enableMask)
{
this.destination = destination;
headerPosition = destination.Position;
DataStream.WriteCString(destination, signature, 4);
DataStream.WriteUInt32(destination, 0xFF);
destination.Seek(8, SeekOrigin.Current);
writer = CriTableWriter.Create(destination, new CriTableWriterSettings()
{
LeaveOpen = true,
EnableMask = enableMask,
MaskXor = 25951,
MaskXorMultiplier = 16661
});
}
}
}
}