`A3DA`: Added support for Raw Data Binary `Aet`: Complete overhaul of AET writer `FARC`: Fixed compressed files not having correct size and encrypted files not being encrypted Updated libdeflate to 1.20
529 lines
18 KiB
C#
529 lines
18 KiB
C#
//Original: https://github.com/blueskythlikesclouds/MikuMikuLibrary/
|
|
|
|
using System.Security.Cryptography;
|
|
using KKdBaseLib;
|
|
using KKdMainLib.IO;
|
|
using MSIO = System.IO;
|
|
|
|
namespace KKdMainLib
|
|
{
|
|
public class FARC : System.IDisposable
|
|
{
|
|
public FARC() => NewFARC();
|
|
public FARC(string file, bool isDirectory = false)
|
|
{ if (isDirectory) DirectoryPath = file; else FilePath = file; NewFARC(); }
|
|
|
|
private void NewFARC() { Files = KKdList<FARCFile>.New; Signature = Farc.FArC; FT = false; }
|
|
|
|
public KKdList<FARCFile> Files = KKdList<FARCFile>.New;
|
|
public Flags FARCFlags;
|
|
public Farc Signature = Farc.FArC;
|
|
public bool FT = false;
|
|
public string FilePath, DirectoryPath;
|
|
public bool HasFiles => !Files.IsNull || Files.Count > 0;
|
|
public int CompressionLevel = 12;
|
|
|
|
private readonly byte[] key = "project_diva.bin".ToASCII();
|
|
|
|
private readonly byte[] keyFT = { 0x13, 0x72, 0xD5, 0x7B, 0x6E, 0x9E,
|
|
0x31, 0xEB, 0xA2, 0x39, 0xB8, 0x3C, 0x15, 0x57, 0xC6, 0xBB };
|
|
|
|
private AesManaged GetAes(byte[] iv = null) =>
|
|
new AesManaged () { KeySize = 128, Key = iv != null ? keyFT : key,
|
|
BlockSize = 128, Mode = iv != null ? CipherMode.CBC : CipherMode.ECB,
|
|
Padding = iv != null ? PaddingMode.PKCS7 : PaddingMode.Zeros, IV = iv ?? new byte[16] };
|
|
|
|
public void Unpack(bool saveToDisk = true)
|
|
{ if (HeaderReader()) FileReader(saveToDisk); }
|
|
|
|
public bool HeaderReader()
|
|
{
|
|
NewFARC();
|
|
if (!File.Exists(FilePath)) return false;
|
|
|
|
Stream reader = File.OpenReader(FilePath);
|
|
DirectoryPath = Path.RemoveExtension(Path.GetFullPath(FilePath));
|
|
Signature = (Farc)reader.RI32E(true);
|
|
if (Signature != Farc.FArc && Signature != Farc.FArC && Signature != Farc.FARC)
|
|
{ reader.Dispose(); return false; }
|
|
|
|
FT = false;
|
|
|
|
int headerLength = reader.RI32E(true);
|
|
if (Signature == Farc.FARC)
|
|
{
|
|
FARCFlags = (Flags)reader.RI32E(true);
|
|
reader.RI32();
|
|
int alignment = reader.RI32E(true);
|
|
|
|
FT = (FARCFlags & Flags.AES) != 0 && (alignment & (alignment - 1)) != 0;
|
|
|
|
if (FT)
|
|
{
|
|
headerLength -= 0x08;
|
|
reader.S(-0x04, SeekOrigin.Current);
|
|
}
|
|
else
|
|
headerLength -= 0x0C;
|
|
}
|
|
|
|
Files.Capacity = 0;
|
|
|
|
if (FT)
|
|
{
|
|
headerLength -= 0x10;
|
|
|
|
byte[] iv = reader.RBy(0x10);
|
|
byte[] header = reader.RBy(headerLength);
|
|
reader.Dispose();
|
|
|
|
using (MSIO.MemoryStream ms = new MSIO.MemoryStream())
|
|
{
|
|
using (AesManaged aes = GetAes(iv))
|
|
using (CryptoStream cryptoStream = new CryptoStream(ms,
|
|
aes.CreateDecryptor(), CryptoStreamMode.Write))
|
|
cryptoStream.Write(header, 0x00, headerLength);
|
|
header = ms.ToArray();
|
|
headerLength = header.Length;
|
|
}
|
|
|
|
reader = File.OpenReader(header);
|
|
int alignment = reader.RI32E(true);
|
|
}
|
|
else
|
|
{
|
|
byte[] header = reader.RBy(headerLength);
|
|
reader.Dispose();
|
|
reader = File.OpenReader(header);
|
|
}
|
|
|
|
bool hasPerFileFlags = false;
|
|
if (Signature == Farc.FARC) {
|
|
if (reader.RI32E(true) == 1) {
|
|
Files.Capacity = reader.RI32E(true);
|
|
hasPerFileFlags = true;
|
|
}
|
|
}
|
|
reader.RI32E(true);
|
|
|
|
if (Files.Capacity == 0)
|
|
{
|
|
int Count = 0;
|
|
long Position = reader.PI64;
|
|
int Size = 0;
|
|
if (hasPerFileFlags)
|
|
Size = sizeof(int) * 4;
|
|
else if (Signature != Farc.FArc)
|
|
Size = sizeof(int) * 3;
|
|
else
|
|
Size = sizeof(int) * 2;
|
|
|
|
while (reader.PI64 < headerLength)
|
|
{
|
|
reader.NT();
|
|
reader.RBy(Size);
|
|
Count++;
|
|
}
|
|
reader.PI64 = Position;
|
|
Files.Capacity = Count;
|
|
}
|
|
|
|
if (hasPerFileFlags)
|
|
for (int i = 0; i < Files.Capacity; i++)
|
|
{
|
|
FARCFile file = default;
|
|
file.Name = reader.NTUTF8();
|
|
file.Offset = reader.RI32E(true);
|
|
file.SizeComp = reader.RI32E(true);
|
|
file.Size = reader.RI32E(true);
|
|
Flags Flags = (Flags)reader.RI32E(true);
|
|
|
|
if (file.Size != 0)
|
|
{
|
|
file.Compressed = true;
|
|
}
|
|
else
|
|
{
|
|
file.Size = file.SizeComp;
|
|
file.SizeComp = 0;
|
|
file.Compressed = false;
|
|
}
|
|
|
|
file.Encrypted = ((FARCFlags | Flags) & Flags.AES) != 0;
|
|
Files.Add(file);
|
|
}
|
|
else if (Signature != Farc.FArc)
|
|
for (int i = 0; i < Files.Capacity; i++)
|
|
{
|
|
FARCFile file = default;
|
|
file.Name = reader.NTUTF8();
|
|
file.Offset = reader.RI32E(true);
|
|
file.SizeComp = reader.RI32E(true);
|
|
file.Size = reader.RI32E(true);
|
|
|
|
if (file.Size != 0)
|
|
{
|
|
file.Compressed = true;
|
|
}
|
|
else
|
|
{
|
|
file.Size = file.SizeComp;
|
|
file.SizeComp = 0;
|
|
file.Compressed = false;
|
|
}
|
|
|
|
file.Encrypted = (FARCFlags & Flags.AES) != 0;
|
|
Files.Add(file);
|
|
}
|
|
else
|
|
for (int i = 0; i < Files.Capacity; i++)
|
|
{
|
|
FARCFile file = default;
|
|
file.Name = reader.NTUTF8();
|
|
file.Offset = reader.RI32E(true);
|
|
file.Size = reader.RI32E(true);
|
|
|
|
file.SizeComp = 0;
|
|
file.Compressed = false;
|
|
file.Encrypted = false;
|
|
Files.Add(file);
|
|
}
|
|
|
|
reader.Dispose();
|
|
return true;
|
|
}
|
|
|
|
public bool Exists(string file)
|
|
{
|
|
if (!HasFiles) return false;
|
|
for (int i = 0; i < Files.Count; i++)
|
|
if (Files[i].Name.ToLower() == file.ToLower()) return true;
|
|
return false;
|
|
}
|
|
|
|
private void FileReader(bool saveToDisk)
|
|
{
|
|
if (saveToDisk)
|
|
MSIO.Directory.CreateDirectory(DirectoryPath);
|
|
|
|
Stream reader = File.OpenReader(FilePath);
|
|
for (int i = 0; i < Files.Count; i++)
|
|
FileReader(i, saveToDisk, ref reader);
|
|
reader.Dispose();
|
|
}
|
|
|
|
public byte[] FileReader(string file)
|
|
{
|
|
if (!HasFiles) return null;
|
|
for (int i = 0; i < Files.Count; i++)
|
|
if (Files[i].Name.ToLower() == file.ToLower())
|
|
return FileReader(i);
|
|
return null;
|
|
}
|
|
|
|
public byte[] FileReader(int i)
|
|
{
|
|
Stream reader = File.OpenReader(FilePath);
|
|
byte[] data = FileReader(i, false, ref reader);
|
|
reader.Dispose();
|
|
return data;
|
|
}
|
|
|
|
public byte[] FileReader(int i, bool saveToDisk, ref Stream reader)
|
|
{
|
|
if (!HasFiles) return null;
|
|
if (i >= Files.Count) return null;
|
|
|
|
FARCFile file = Files[i];
|
|
file.Data = null;
|
|
|
|
reader.S(file.Offset, SeekOrigin.Begin);
|
|
|
|
if (Signature == Farc.FArc)
|
|
{
|
|
file.DataComp = null;
|
|
file.Data = reader.RBy(file.Size);
|
|
}
|
|
else if (Signature == Farc.FArC)
|
|
{
|
|
if (file.Compressed)
|
|
{
|
|
file.DataComp = reader.RBy(file.SizeComp);
|
|
file.Data = file.DataComp.InflateGZip(file.Size);
|
|
}
|
|
else
|
|
{
|
|
file.DataComp = null;
|
|
file.Data = reader.RBy(file.Size);
|
|
}
|
|
}
|
|
else if (file.Compressed || file.Encrypted)
|
|
{
|
|
int tempSize = file.Encrypted ? file.SizeComp.A(0x10) : file.SizeComp;
|
|
|
|
byte[] temp;
|
|
if (file.Encrypted)
|
|
if (FT)
|
|
{
|
|
file.SizeComp -= 0x10;
|
|
tempSize -= 0x10;
|
|
|
|
byte[] iv = reader.RBy(0x10);
|
|
using (MSIO.MemoryStream ms = new MSIO.MemoryStream())
|
|
{
|
|
using (AesManaged aes = GetAes(iv))
|
|
using (CryptoStream cryptoStream = new CryptoStream(ms,
|
|
aes.CreateDecryptor(), CryptoStreamMode.Write))
|
|
cryptoStream.Write(reader.RBy(tempSize), 0x00, tempSize);
|
|
temp = ms.ToArray();
|
|
file.SizeComp = temp.Length;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
using (MSIO.MemoryStream ms = new MSIO.MemoryStream())
|
|
{
|
|
using (AesManaged aes = GetAes())
|
|
using (CryptoStream cryptoStream = new CryptoStream(ms,
|
|
aes.CreateDecryptor(), CryptoStreamMode.Write))
|
|
cryptoStream.Write(reader.RBy(tempSize), 0x00, tempSize);
|
|
temp = ms.ToArray();
|
|
}
|
|
}
|
|
else
|
|
temp = reader.RBy(tempSize);
|
|
|
|
if (file.Compressed)
|
|
{
|
|
file.DataComp = temp;
|
|
file.Data = file.DataComp.InflateGZip(file.Size);
|
|
}
|
|
else
|
|
{
|
|
file.DataComp = null;
|
|
file.Data = temp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
file.DataComp = null;
|
|
file.Data = reader.RBy(file.Size);
|
|
}
|
|
|
|
if (saveToDisk)
|
|
{
|
|
File.WriteAllBytes(Path.Combine(DirectoryPath, file.Name), file.Data);
|
|
file.DataComp = null;
|
|
file.Data = null;
|
|
}
|
|
|
|
Files[i] = file;
|
|
return Files[i].Data;
|
|
}
|
|
|
|
public void Pack()
|
|
{
|
|
NewFARC();
|
|
string[] files = Directory.GetFiles(DirectoryPath);
|
|
Files.Capacity = files.Length;
|
|
|
|
bool compressed = false;
|
|
bool encrypted = false;
|
|
switch (Signature)
|
|
{
|
|
case Farc.FARC:
|
|
compressed = (FARCFlags & Flags.GZip) != 0;
|
|
encrypted = (FARCFlags & Flags.AES) != 0;
|
|
break;
|
|
case Farc.FArC:
|
|
FARCFlags = Flags.None;
|
|
compressed = true;
|
|
break;
|
|
default:
|
|
FARCFlags = Flags.None;
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < files.Length; i++)
|
|
Files.Add(new FARCFile { Name = Path.GetFileName(files[i]),
|
|
Data = File.ReadAllBytes(files[i]), Compressed = compressed, Encrypted = encrypted });
|
|
files = null;
|
|
Save();
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
if (!HasFiles || (Signature != Farc.FArc && Signature != Farc.FArC && Signature != Farc.FARC)) return;
|
|
|
|
for (int i = 0; i < Files.Count; i++)
|
|
{
|
|
string ext = Path.GetExtension(Files[i].Name).ToLower();
|
|
if (ext == ".a3da" || ext == ".diva" || ext == ".farc" || ext == ".vag")
|
|
{ Signature = Farc.FArc; break; }
|
|
}
|
|
|
|
Stream writer = File.OpenWriter(DirectoryPath + ".farc", true);
|
|
writer.WE((int)Signature, true);
|
|
|
|
using (Stream headerWriter = File.OpenWriter())
|
|
{
|
|
switch (Signature)
|
|
{
|
|
case Farc.FArc:
|
|
headerWriter.WE(0x01, true);
|
|
for (int i = 0; i < Files.Count; i++)
|
|
{ headerWriter.W(Files[i].Name + "\0"); headerWriter.W(0x00L); }
|
|
break;
|
|
case Farc.FArC:
|
|
headerWriter.WE(0x01, true);
|
|
for (int i = 0; i < Files.Count; i++)
|
|
{ headerWriter.W(Files[i].Name + "\0"); headerWriter.W(0x00L); headerWriter.W(0x00); }
|
|
break;
|
|
case Farc.FARC:
|
|
headerWriter.WE((int)FARCFlags, true);
|
|
headerWriter.W (0x00);
|
|
headerWriter.WE(0x40, true);
|
|
headerWriter.W (0x00);
|
|
headerWriter.W (0x00);
|
|
for (int i = 0; i < Files.Count; i++)
|
|
{ headerWriter.W(Files[i].Name + "\0"); headerWriter.W(0x00L); headerWriter.W(0x00L); }
|
|
break;
|
|
}
|
|
writer.WE(headerWriter.L, true);
|
|
writer.W (headerWriter.ToArray(true));
|
|
}
|
|
|
|
int align = writer.P.A(0x10) - writer.P;
|
|
for (int i1 = 0; i1 < align; i1++)
|
|
writer.W((byte)(Signature == Farc.FArc ? 0x00 : 0x78));
|
|
|
|
writer.F();
|
|
for (int i = 0; i < Files.Count; i++)
|
|
CompressStuff(i, ref writer);
|
|
|
|
writer.P = Signature == Farc.FARC ? 0x1C : 0x0C;
|
|
switch (Signature)
|
|
{
|
|
case Farc.FArc:
|
|
for (int i = 0; i < Files.Count; i++)
|
|
{
|
|
FARCFile file = Files[i];
|
|
writer.W (file.Name + "\0");
|
|
writer.WE(file.Offset, true);
|
|
writer.WE(file.Size , true);
|
|
}
|
|
break;
|
|
case Farc.FArC:
|
|
for (int i = 0; i < Files.Count; i++)
|
|
{
|
|
FARCFile file = Files[i];
|
|
writer.W (file.Name + "\0");
|
|
writer.WE(file.Offset , true);
|
|
if (file.Compressed)
|
|
{
|
|
writer.WE(file.SizeComp, true);
|
|
writer.WE(file.Size , true);
|
|
}
|
|
else
|
|
{
|
|
writer.WE(file.Size , true);
|
|
writer.WE(0x00 , true);
|
|
}
|
|
}
|
|
break;
|
|
case Farc.FARC:
|
|
for (int i = 0; i < Files.Count; i++)
|
|
{
|
|
FARCFile file = Files[i];
|
|
writer.W (file.Name + "\0");
|
|
writer.WE(file.Offset , true);
|
|
if (file.Compressed)
|
|
{
|
|
writer.WE(file.SizeComp, true);
|
|
writer.WE(file.Size , true);
|
|
}
|
|
else
|
|
{
|
|
writer.WE(file.Size , true);
|
|
writer.WE(0x00 , true);
|
|
}
|
|
writer.WE((uint)((file.Compressed ? Flags.GZip : 0x00)
|
|
| (file.Encrypted ? Flags.AES : 0x00)), true);
|
|
}
|
|
break;
|
|
}
|
|
writer.Dispose();
|
|
}
|
|
|
|
private void CompressStuff(int i, ref Stream writer)
|
|
{
|
|
FARCFile file = Files[i];
|
|
file.Offset = writer.P;
|
|
file.Size = file.Data.Length;
|
|
|
|
byte[] data = file.Data;
|
|
if (file.Compressed)
|
|
data = file.Data.DeflateGZip(CompressionLevel);
|
|
file.SizeComp = data.Length;
|
|
|
|
if (file.Encrypted)
|
|
{
|
|
int alignLength = data.Length.A(0x40);
|
|
byte[] tempData = new byte[alignLength];
|
|
System.Array.Copy(data, tempData, data.Length);
|
|
for (int i1 = file.Data.Length; i1 < alignLength; i1++) tempData[i1] = 0x78;
|
|
data = Encrypt(tempData);
|
|
}
|
|
writer.W(data);
|
|
|
|
if (Signature != Farc.FARC)
|
|
{
|
|
int Align = writer.P.A(0x10) - writer.P;
|
|
for (int i1 = 0; i1 < Align; i1++)
|
|
writer.W((byte)(Signature == Farc.FArc ? 0x00 : 0x78));
|
|
}
|
|
Files[i] = file;
|
|
}
|
|
|
|
private byte[] Encrypt(byte[] data)
|
|
{
|
|
MSIO.MemoryStream ms = new MSIO.MemoryStream();
|
|
using (AesManaged aes = GetAes())
|
|
using (CryptoStream cryptoStream = new CryptoStream(ms,
|
|
aes.CreateDecryptor(), CryptoStreamMode.Write))
|
|
cryptoStream.Write(data, 0x00, data.Length);
|
|
return ms.ToArray();
|
|
}
|
|
|
|
public void Dispose() => NewFARC();
|
|
|
|
public struct FARCFile
|
|
{
|
|
public string Name;
|
|
public int Offset;
|
|
public int Size;
|
|
public int SizeComp;
|
|
public byte[] Data;
|
|
public byte[] DataComp;
|
|
public bool Compressed;
|
|
public bool Encrypted;
|
|
|
|
public override string ToString() => Name;
|
|
}
|
|
|
|
public enum Farc : int
|
|
{
|
|
FArc = 0x46417263,
|
|
FArC = 0x46417243,
|
|
FARC = 0x46415243,
|
|
}
|
|
|
|
public enum Flags : int
|
|
{
|
|
None = 0b000,
|
|
GZip = 0b010,
|
|
AES = 0b100,
|
|
}
|
|
}
|
|
}
|