//Original: https://github.com/blueskythlikesclouds/MikuMikuLibrary/ using System.IO.Compression; using System.Security.Cryptography; using KKdMainLib.IO; using MSIO = System.IO; namespace KKdMainLib { public class FARC { public FARC() => NewFARC(); public FARC(string File, bool IsDirectory = false) { if (IsDirectory) DirectoryPath = File; else FilePath = File; NewFARC(); } private void NewFARC() { Files = null; Signature = Farc.FArC; CBC = FT = false; } public FARCFile[] Files = null; public Type FARCType; public Farc Signature = Farc.FArC; public string FilePath, DirectoryPath; private bool CBC, FT; 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 }; AesManaged GetAes(bool isFT, byte[] iv) { AesManaged AesManaged = new AesManaged { KeySize = 128, Key = isFT ? KeyFT : Key, BlockSize = 128, Mode = isFT ? CipherMode.CBC : CipherMode.ECB, Padding = PaddingMode.Zeros, IV = iv ?? new byte[16] }; return AesManaged; } public void UnPack(bool SaveToDisk = true) { HeaderReader().FileReader(); if (SaveToDisk) this.SaveToDisk(); } private FARC HeaderReader() { NewFARC(); if (!File.Exists(FilePath)) return this; Stream reader = File.OpenReader(FilePath); DirectoryPath = Path.GetFullPath(FilePath).Replace(Path.GetExtension(FilePath), ""); Signature = (Farc)reader.ReadInt32Endian(true); if (Signature != Farc.FArc && Signature != Farc.FArC && Signature != Farc.FARC) { reader.Close(); return this; } int HeaderLength = reader.ReadInt32Endian(true); if (Signature == Farc.FARC) { FARCType = (Type)reader.ReadInt32Endian(true); reader.ReadInt32(); int FARCMode = reader.ReadInt32Endian(true); FT = FARCMode == 0x10; CBC = FARCMode != 0x10 && FARCMode != 0x40; if (CBC && FARCType.HasFlag(Type.ECB)) { reader.Close(); 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 (CryptoStream cryptoStream = new CryptoStream(stream, GetAes(true, null).CreateDecryptor(), CryptoStreamMode.Read)) cryptoStream.Read(Header, 0x00, HeaderLength - 0x08); Header = SkipData(Header, 0x10); reader = File.OpenReader(Header); FARCMode = reader.ReadInt32Endian(true); FT = FARCMode == 0x10; } } if (Signature == Farc.FARC) if (reader.ReadInt32Endian(true) == 1) Files = new FARCFile[reader.ReadInt32Endian(true)]; reader.ReadInt32(); if (Files == null) { int Count = 0; long Position = reader.LongPosition; while (reader.LongPosition < HeaderLength) { reader.NullTerminated(); reader.ReadInt32(); if (Signature != Farc.FArc ) reader.ReadInt32(); reader.ReadInt32(); if (Signature == Farc.FARC && FT) reader.ReadInt32(); Count++; } reader.LongPosition = Position; Files = new FARCFile[Count]; } for (int i = 0; i < Files.Length; i++) { Files[i].Name = reader.NullTerminatedUTF8(); Files[i].Offset = reader.ReadInt32Endian(true); if (Signature != Farc.FArc) Files[i].SizeComp = reader.ReadInt32Endian(true); Files[i].SizeUnc = reader.ReadInt32Endian(true); if (Signature == Farc.FARC && FT) Files[i].Type = (Type)reader.ReadInt32Endian(true); } reader.Close(); return this; } private FARC FileReader() { if (Files == null) return this; if (Files.Length < 1) return this; if (Signature != Farc.FARC) { for (int i = 0; i < Files.Length; i++) if (Signature == Farc.FArC) using (MSIO.MemoryStream memorystream = new MSIO.MemoryStream( File.ReadAllBytes(FilePath, Files[i].SizeComp, Files[i].Offset))) { GZipStream gZipStream = new GZipStream(memorystream, CompressionMode.Decompress); Files[i].Data = new byte[Files[i].SizeUnc]; gZipStream.Read(Files[i].Data, 0, Files[i].SizeUnc); } else Files[i].Data = File.ReadAllBytes(FilePath, Files[i].SizeUnc, Files[i].Offset); return this; } for (int i = 0; i < Files.Length; i++) { int FileSize = FARCType.HasFlag(Type.ECB) || Files[i].Type.HasFlag(Type.ECB) ? Files[i].SizeComp.Align(0x10) : Files[i].SizeComp; MSIO.FileStream stream = new MSIO.FileStream(FilePath, MSIO.FileMode.Open, MSIO.FileAccess.ReadWrite, MSIO.FileShare.ReadWrite); stream.Seek(Files[i].Offset, 0); Files[i].Data = new byte[FileSize]; bool Encrypted = false; if (FARCType.HasFlag(Type.ECB)) { if ((FT && Files[i].Type.HasFlag(Type.ECB)) || CBC) { using (CryptoStream cryptoStream = new CryptoStream(stream, GetAes(true, null).CreateDecryptor(), CryptoStreamMode.Read)) cryptoStream.Read(Files[i].Data, 0, FileSize); Files[i].Data = SkipData(Files[i].Data, 0x10); } else using (CryptoStream cryptoStream = new CryptoStream(stream, GetAes(false, null).CreateDecryptor(), CryptoStreamMode.Read)) cryptoStream.Read(Files[i].Data, 0, FileSize); Encrypted = true; } bool Compressed = false; if (((FT && Files[i].Type.HasFlag(Type.GZip)) || FARCType.HasFlag(Type.GZip)) && Files[i].SizeUnc > 0) { GZipStream gZipStream; if (Encrypted) { gZipStream = new GZipStream(new MSIO.MemoryStream(Files[i].Data), CompressionMode.Decompress); stream.Close(); } else gZipStream = new GZipStream(stream, CompressionMode.Decompress); byte[] Temp = new byte[Files[i].SizeUnc]; gZipStream.Read(Temp, 0, Files[i].SizeUnc); Files[i].Data = Temp; Compressed = true; } if (!Encrypted && !Compressed) { Files[i].Data = new byte[Files[i].SizeUnc]; stream.Read(Files[i].Data, 0, Files[i].SizeUnc); stream.Close(); } } return this; } private void SaveToDisk() { if (DirectoryPath == null || Files == null) return; if (DirectoryPath == "" || Files.Length < 1) return; MSIO.Directory.CreateDirectory(DirectoryPath); for (int i = 0; i < Files.Length; i++) { File.WriteAllBytes(Path.Combine(DirectoryPath, Files[i].Name), Files[i].Data); Files[i].Data = null; } } private byte[] SkipData(byte[] Data, int Skip) { byte[] SkipData = new byte[Data.Length - Skip]; for (int i = 0; i < Data.Length - Skip; i++) SkipData[i] = Data[i + Skip]; return SkipData; } public FARC Pack() { NewFARC(); string[] files = Directory.GetFiles(DirectoryPath); Files = new FARCFile[files.Length]; for (int i = 0; i < files.Length; i++) Files[i] = new FARCFile { Name = Path.GetFileName(files[i]), Data = File.ReadAllBytes(files[i]) }; files = null; return this; } public void CompressStuff() { for (int i = 0; i < Files.Length; i++) { string ext = Path.GetExtension(Files[i].Name).ToLower(); if (ext == ".a3da" || ext == ".diva" || ext == ".vag") Signature = Farc.FArc; } Stream writer = File.OpenWriter(DirectoryPath + ".farc", true); writer.WriteEndian((int)Signature, true); Stream HeaderWriter = File.OpenWriter(); if (Signature == Farc.FArc) HeaderWriter.WriteEndian(0x20, true); else if (Signature == Farc.FArC) HeaderWriter.WriteEndian(0x10, true); else if (Signature == Farc.FARC) { HeaderWriter.WriteEndian((int)FARCType, true); HeaderWriter.Write (0x00); HeaderWriter.WriteEndian(0x40, true); HeaderWriter.Write (0x00); } int HeaderPartLength = Signature == Farc.FArc ? 0x09 : 0x0D; for (int i = 0; i < Files.Length; i++) HeaderWriter.Length += Path.GetFileName(Files[i].Name).Length + HeaderPartLength; writer.WriteEndian(HeaderWriter.Length, true); writer.Write(HeaderWriter.ToArray(true)); HeaderWriter = null; int Align = writer.Position.Align(0x10) - writer.Position; for (int i1 = 0; i1 < Align; i1++) writer.WriteByte((byte)(Signature == Farc.FArc ? 0x00 : 0x78)); for (int i = 0; i < Files.Length; i++) CompressStuff(i, ref Files, ref writer); writer.Position = Signature == Farc.FARC ? 0x1C : 0x0C; for (int i = 0; i < Files.Length; i++) { writer.Write(Path.GetFileName(Files[i].Name) + "\0"); writer.WriteEndian(Files[i].Offset, true); if (Signature != Farc.FArc) writer.WriteEndian(Files[i].SizeComp, true); writer.WriteEndian(Files[i].SizeUnc, true); } writer.Close(); } private void CompressStuff(int i, ref FARCFile[] Files, ref Stream writer) { Files[i].Offset = writer.Position; Files[i].SizeUnc = Files[i].Data.Length; Files[i].Type = Type.None; if (Signature == Farc.FArC || (Signature == Farc.FARC && FARCType.HasFlag(Type.GZip))) { Files[i].Type |= Type.GZip; MSIO.MemoryStream stream = new MSIO.MemoryStream(); using (GZipStream gZipStream = new GZipStream(stream, CompressionMode.Compress)) gZipStream.Write(Files[i].Data, 0, Files[i].Data.Length); Files[i].Data = stream.ToArray(); stream.Dispose(); Files[i].SizeComp = Files[i].Data.Length; } if (Signature == Farc.FARC && FARCType.HasFlag(Type.ECB)) { int AlignData = Files[i].Data.Length.Align(0x40); byte[] Data = new byte[AlignData]; for (int i1 = 0; i1 < AlignData ; i1++) Data[i1] = 0x78; for (int i1 = 0; i1 < Files[i].Data.Length; i1++) Data[i1] = Files[i].Data[i1]; Files[i].Data = Encrypt(Data, false); } writer.Write(Files[i].Data); Files[i].Data = null; if (Signature != Farc.FARC) { int Align = writer.Position.Align(0x20) - writer.Position; for (int i1 = 0; i1 < Align; i1++) writer.WriteByte((byte)(Signature == Farc.FArc ? 0x00 : 0x78)); } } private byte[] Encrypt(byte[] Data, bool isFT) { MSIO.MemoryStream stream = new MSIO.MemoryStream(); using (CryptoStream cryptoStream = new CryptoStream(stream, GetAes(isFT, null).CreateEncryptor(), CryptoStreamMode.Write)) cryptoStream.Write(Data, 0, Data.Length); return stream.ToArray(); } 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, ECB = 0b100, } } }