Files
korenkonder_PD_Tool/KKdMainLib/FARC.cs
T
2020-06-03 01:45:36 +03:00

352 lines
13 KiB
C#

//Original: https://github.com/blueskythlikesclouds/MikuMikuLibrary/
using System.IO.Compression;
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() { if (!MSIO.File.Exists("libdeflate.dll"))
File.WriteAllBytes("libdeflate.dll", Properties.Resources.libdeflate);
Files = KKdList<FARCFile>.New; Signature = Farc.FArC; Format = Format.DT; }
public KKdList<FARCFile> Files = KKdList<FARCFile>.New;
public Type FARCType;
public Farc Signature = Farc.FArC;
public Format Format = Format.DT;
public string FilePath, DirectoryPath;
public bool HasFiles => Files.IsNull ? false : Files.Count > 0;
public int CompressionLevel = 12;
private readonly byte[] key = Text.ToASCII("project_diva.bin");
private readonly byte[] keyFT = { 0x13, 0x72, 0xD5, 0x7B, 0x6E, 0x9E,
0x31, 0xEB, 0xA2, 0x39, 0xB8, 0x3C, 0x15, 0x57, 0xC6, 0xBB };
private AesManaged GetAes(bool isFT, byte[] iv) =>
new AesManaged () { KeySize = 128, Key = isFT ? keyFT : key,
BlockSize = 128, Mode = isFT ? CipherMode.CBC : CipherMode.ECB,
Padding = PaddingMode.Zeros, IV = iv ?? new byte[16] };
public void Unpack(bool saveToDisk = true)
{ if (HeaderReader()) { FileReader(); if (saveToDisk) SaveToDisk(); } }
public bool HeaderReader()
{
NewFARC();
if (!File.Exists(FilePath)) return false;
Stream reader = File.OpenReader(FilePath);
DirectoryPath = Path.GetFullPath(FilePath).Replace(Path.GetExtension(FilePath), "");
Signature = (Farc)reader.RI32E(true);
if (Signature != Farc.FArc && Signature != Farc.FArC && Signature != Farc.FARC)
{ reader.Dispose(); return false; }
int headerLength = reader.RI32E(true);
if (Signature == Farc.FARC)
{
FARCType = (Type)reader.RI32E(true);
reader.RI32();
int farcMode = reader.RI32E(true);
Format = (FARCType & Type.Enc) != 0 && (farcMode & (farcMode - 1)) != 0 ? Format.FT : Format.DT;
if (Format == Format.FT && (FARCType & Type.Enc) != 0)
{
reader.Dispose();
byte[] header = new byte[headerLength - 0x08];
MSIO.FileStream stream = new MSIO.FileStream(FilePath, MSIO.FileMode.Open,
MSIO.FileAccess.ReadWrite, MSIO.FileShare.ReadWrite) { Position = 0x10 };
using (AesManaged aes = GetAes(true, null))
using (CryptoStream cryptoStream = new CryptoStream(stream,
aes.CreateDecryptor(), CryptoStreamMode.Read))
cryptoStream.Read(header, 0x00, headerLength - 0x08);
header = SkipData(header, 0x10);
reader = File.OpenReader(header);
farcMode = reader.RI32E(true);
}
}
if (Signature == Farc.FARC)
if (reader.RI32E(true) == 1)
Files.Capacity = reader.RI32E(true);
reader.RI32();
if (Files.Capacity == 0)
{
int Count = 0;
long Position = reader.PI64;
while (reader.PI64 < headerLength)
{
reader.NT();
reader.RI32();
if (Signature != Farc.FArc) reader.RI32();
reader.RI32();
if (Signature == Farc.FARC && Format == Format.FT) reader.RI32();
Count++;
}
reader.PI64 = Position;
Files.Capacity = Count;
}
for (int i = 0; i < Files.Capacity; i++)
{
FARCFile file = default;
file.Name = reader.NTUTF8();
file.Offset = reader.RI32E(true);
if (Signature != Farc.FArc) file.SizeComp = reader.RI32E(true);
file.SizeUnc = reader.RI32E(true);
if (Signature == Farc.FARC && Format == Format.FT)
file.Type = (Type)reader.RI32E(true);
Files.Add(file);
}
reader.Dispose();
return true;
}
private void FileReader()
{ for (int i = 0; i < Files.Count; i++) FileReader(i); }
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 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;
}
public byte[] FileReader(int i)
{
if (!HasFiles) return null;
if (i >= Files.Count) return null;
FARCFile file = Files[i];
if (Signature != Farc.FARC)
{
if (Signature == Farc.FArC)
using (MSIO.MemoryStream memorystream = new MSIO.MemoryStream(
File.ReadAllBytes(FilePath, file.SizeComp, file.Offset)))
using (GZipStream gZipStream = new GZipStream(memorystream, CompressionMode.Decompress))
{
file.Data = new byte[file.SizeUnc];
gZipStream.Read(file.Data, 0, file.SizeUnc);
}
else file.Data = File.ReadAllBytes(FilePath, file.SizeUnc, file.Offset);
Files[i] = file;
return file.Data;
}
int FileSize = ((FARCType | file.Type) & Type.Enc) != 0 ? file.SizeComp.A(0x10)
: (((FARCType | file.Type) & Type.GZip) != 0 ? file.SizeComp : file.SizeUnc);
using (Stream stream = File.OpenReader(FilePath))
{
stream.S(file.Offset, 0);
file.Data = stream.RBy(FileSize);
}
if ((FARCType & Type.Enc) != 0)
{
if (Format == Format.FT && (file.Type & Type.Enc) != 0)
{
using (AesManaged aes = GetAes(true, null))
using (CryptoStream cryptoStream = new CryptoStream(new MSIO.MemoryStream(file.Data),
aes.CreateDecryptor(), CryptoStreamMode.Read))
cryptoStream.Read(file.Data, 0, FileSize);
file.Data = SkipData(file.Data, 0x10);
}
else
using (AesManaged aes = GetAes(false, null))
using (CryptoStream cryptoStream = new CryptoStream(new MSIO.MemoryStream(file.Data),
aes.CreateDecryptor(), CryptoStreamMode.Read))
cryptoStream.Read(file.Data, 0, FileSize);
}
if (((file.Type | FARCType) & Type.GZip) != 0 && file.SizeUnc > 0)
file.Data = file.Data.InflateGZip(file.SizeUnc);
Files[i] = file;
return file.Data;
}
private void SaveToDisk()
{
if (DirectoryPath == null || Files.IsNull ) return;
if (DirectoryPath == "" || Files.Count < 1) return;
MSIO.Directory.CreateDirectory(DirectoryPath);
for (int i = 0; i < Files.Count; i++)
{
FARCFile file = Files[i];
if (file.Data != null)
File.WriteAllBytes(Path.Combine(DirectoryPath, file.Name), file.Data);
}
}
private byte[] SkipData(byte[] data, int skip)
{
byte[] skipData = new byte[data.Length - skip];
System.Array.Copy(data, skip, skipData, 0, data.Length - skip);
return skipData;
}
public void Pack(Farc signature = Farc.FArC)
{
NewFARC();
string[] files = Directory.GetFiles(DirectoryPath);
Files.Capacity = files.Length;
for (int i = 0; i < files.Length; i++)
Files.Add(new FARCFile { Name = Path.GetFileName(files[i]), Data = File.ReadAllBytes(files[i]) });
files = null;
Signature = signature;
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 == ".vag")
{ Signature = Farc.FArc; break; }
}
Stream writer = File.OpenWriter(DirectoryPath + ".farc", true);
writer.WE((int)Signature, true);
using (Stream headerWriter = File.OpenWriter())
{
if (Signature == Farc.FArc) headerWriter.WE(0x20, true);
else if (Signature == Farc.FArC) headerWriter.WE(0x10, true);
else if (Signature == Farc.FARC)
{
headerWriter.WE((int)FARCType, true);
headerWriter.W (0x00);
headerWriter.WE(0x40, true);
headerWriter.W (0x00);
headerWriter.W (0x00);
}
if (Signature != Farc.FArc)
for (int i = 0; i < Files.Count; i++)
{ headerWriter.W(Files[i].Name + "\0"); headerWriter.W(0x00L); headerWriter.W(0x00); }
else
for (int i = 0; i < Files.Count; i++)
{ headerWriter.W(Files[i].Name + "\0"); headerWriter.W(0x00L); }
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;
for (int i = 0; i < Files.Count; i++)
{
FARCFile file = Files[i];
writer.W (file.Name + "\0");
writer.WE(file.Offset, true);
if (Signature != Farc.FArc) writer.WE(file.SizeComp, true);
writer.WE(file.SizeUnc, true);
}
writer.Dispose();
}
private void CompressStuff(int i, ref Stream writer)
{
FARCFile file = Files[i];
file.Offset = writer.P;
file.SizeUnc = file.Data.Length;
file.Type = Type.None;
byte[] data = file.Data;
if (Signature == Farc.FArC || (Signature == Farc.FARC && (FARCType & Type.GZip) != 0))
{
file.Type |= Type.GZip;
data = file.Data.DeflateGZip(CompressionLevel);
file.SizeComp = data.Length;
}
if (Signature == Farc.FARC && (FARCType & Type.Enc) != 0)
{
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, false);
file.SizeComp = data.Length;
}
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, bool isFT)
{
MSIO.MemoryStream stream = new MSIO.MemoryStream();
using (AesManaged aes = GetAes(isFT, null))
using (CryptoStream cryptoStream = new CryptoStream(stream,
aes.CreateEncryptor(), CryptoStreamMode.Write))
cryptoStream.Write(data, 0, data.Length);
return stream.ToArray();
}
public void Dispose() => NewFARC();
public struct FARCFile
{
public int Offset;
public int SizeComp;
public int SizeUnc;
public Type Type;
public byte[] Data;
public string Name;
public override string ToString() => Name;
}
public enum Farc : int
{
FArc = 0x46417263,
FArC = 0x46417243,
FARC = 0x46415243,
}
public enum Type : int
{
None = 0b000,
GZip = 0b010,
Enc = 0b100,
}
}
}