private TellTaleFileStructureInfo PrepareFileInfo(BinReader reader) { var result = new TellTaleFileStructureInfo(); result.FileVersion = 10; // For neatness, we reset to start of file, but skipping the format FourCC. reader.Position = 4; result.BlockSizeUncompressed = reader.ReadU32LE(); uint blockCount = reader.ReadU32LE(); ulong blockOffset = reader.ReadU64LE(); // first block offset result.VirtualBlocksOffset = blockOffset; for (int i = 0; i < blockCount; i++) { ulong nextBlockOffset = reader.ReadU64LE(); result.BlockOffsets.Add(blockOffset); result.BlockSizesCompressed.Add((uint)(nextBlockOffset - blockOffset)); blockOffset = nextBlockOffset; } return(result); }
protected override Stream CreateStream(string path) { var fileStream = File.OpenRead(path); using (BinReader reader = new BinReader(fileStream)) { TellTaleFileStructureInfo fileInfo = PrepareFileInfo(reader); if (fileInfo == null) { return(null); } byte[] key = FindKey(reader, fileInfo, TellTaleKeyManager.Instance.KeysTTArch); // TODO: Make sure key was found return(new TellTaleBlowfishZlibStream(fileStream, fileInfo, key, fileInfo.FileVersion >= 7)); } }
private byte[] FindKey(BinReader reader, TellTaleFileStructureInfo fileInfo, IEnumerable <TellTaleKeyInfo> keys) { const int decompressSize = 4; const int readSize = 4096; byte[] readBytes = new byte[readSize]; byte[] testBytes = new byte[readSize]; byte[] deflateBytes = new byte[decompressSize]; reader.Position = fileInfo.VirtualBlocksOffset; reader.Read(readBytes, 0, readSize); using (MemoryStream stream = new MemoryStream(testBytes)) { foreach (var info in keys) { var testBlowfish = new Blowfish(info.Key, true); testBlowfish.Decipher(readBytes, testBytes, readSize); stream.Position = 0; using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, true)) { try { int bytesRead = 0; // FIXME: Sometimes DeflateStream.Read reads 0 bytes... while (bytesRead == 0) { bytesRead = deflateStream.Read(deflateBytes, 0, decompressSize); } } catch (InvalidDataException) { // Since a wrong key may cause invalid zlib input, // we catch the exception for that, and continue to the next key continue; } } if (deflateBytes[0] == '3' && deflateBytes[1] == 'A' && deflateBytes[2] == 'T' && deflateBytes[3] == 'T') { return(info.Key); } } } return(null); }
private byte[] FindKey(BinReader reader, TellTaleFileStructureInfo fileInfo, IEnumerable <TellTaleKeyInfo> keys) { // ttarch1 doesn't have a nice magic FourCC to check for. // Instead, we have two possibilities: // - if the info block is encrypted, we can check if it follows known info block "rules": // - its folder count is a small number (usually 1 or 2) // - its folder name lengths aren't insane // - its folder names decode to typical ASCII characters // - etc. // - if it's not, we can instead check if the first block starts with something we can recognize if (fileInfo.IsInfoEncrypted) { return(DetermineKeyByInfoTable(reader, fileInfo, keys)); } else { return(DetermineKeyByFirstBlock(reader, fileInfo, keys)); } }
private static byte[] DetermineKeyByInfoTable(BinReader reader, TellTaleFileStructureInfo fileInfo, IEnumerable <TellTaleKeyInfo> keys) { byte[] encryptedTable = fileInfo.ReadInfoBlock(reader, null); byte[] decryptedTable = new byte[encryptedTable.Length]; foreach (var info in keys) { var blowfish = new Blowfish(info.Key, fileInfo.FileVersion >= 7); blowfish.Decipher(encryptedTable, decryptedTable, (uint)(encryptedTable.Length / 8) * 8); // Now we do our assertions which should be true if the key was right: using (MemoryStream infoStream = new MemoryStream(decryptedTable)) { using (var infoReader = new BinReader(infoStream)) { uint folderCount = infoReader.ReadU32LE(); // The maximum number of folders is somewhat arbitrary. // In games tested, I haven't seen folder counts this high. if (folderCount > 128) { Trace.TraceInformation("Key check - folder count: {0}", folderCount); continue; } // We just check the first folder: uint nameSize = infoReader.ReadU32LE(); if (nameSize > 300) { Trace.TraceInformation("Key check - folder name size: {0}", nameSize); continue; } return(info.Key); } } } return(null); }
protected override Stream CreateStream(string path) { var fileStream = File.OpenRead(path); using (BinReader reader = new BinReader(fileStream)) { FourCC fourCC = reader.ReadFourCC(); switch (fourCC.Name) { case "ECTT": // Compressed and encrypted TellTaleFileStructureInfo fileInfo = PrepareFileInfo(reader); byte[] key = FindKey(reader, fileInfo, TellTaleKeyManager.Instance.KeysTTArch2); if (key == null) { throw new ScummRevisitedException("Couldn't determine blowfish key"); } // ttarch2 always uses modified blowfish algorithm return(new TellTaleBlowfishZlibStream(fileStream, fileInfo, key, true)); case "ZCTT": // Compressed and unencrypted fileInfo = PrepareFileInfo(reader); return(new TellTaleBlowfishZlibStream(fileStream, fileInfo)); case "3ATT": case "NCTT": // Uncompressed and unencrypted return(fileStream); default: // not TTARCH2 fileStream.Dispose(); return(null); } } }
private static byte[] DetermineKeyByFirstBlock(BinReader reader, TellTaleFileStructureInfo fileInfo, IEnumerable <TellTaleKeyInfo> keys) { // TODO: Implement key finding return(HexUtils.StringToByteArray(KEY)); }
public static TellTaleFileStructureInfo PrepareFileInfo(BinReader reader) { var result = new TellTaleFileStructureInfo(); // For neatness, we reset to start of file, but skipping the format FourCC. reader.Position = 0; result.FileVersion = reader.ReadU32LE(); uint infoEncrypted = reader.ReadU32LE(); if (result.FileVersion > 9 || infoEncrypted > 1) { // Not a ttarch file, or unsupported return(null); } uint type3 = reader.ReadU32LE(); uint filesFormat = 0; uint blockCount = 0; uint dataSize = 0; uint unknown1 = 0; uint unknown2 = 0; uint unknown3 = 0; uint unknown4 = 0; byte unknown5 = 0; uint unknown6 = 0; ulong[] blockOffsets = null; if (result.FileVersion >= 3) { filesFormat = reader.ReadU32LE(); blockCount = reader.ReadU32LE(); blockOffsets = new ulong[blockCount]; if (blockCount > 0) { ulong blockOffset = 0; for (int i = 0; i < blockCount; i++) { uint size = reader.ReadU32LE(); result.BlockSizesCompressed.Add(size); blockOffsets[i] = blockOffset; blockOffset += size; } } dataSize = reader.ReadU32LE(); } if (result.FileVersion >= 4) { unknown1 = reader.ReadU32LE(); unknown2 = reader.ReadU32LE(); } if (result.FileVersion >= 7) { unknown3 = reader.ReadU32LE(); unknown4 = reader.ReadU32LE(); result.BlockSizeUncompressed = reader.ReadU32LE() * 1024; } if (result.FileVersion >= 8) { unknown5 = reader.ReadU8(); } if (result.FileVersion >= 9) { unknown6 = reader.ReadU32LE(); } result.HasInfo = true; result.IsInfoEncrypted = infoEncrypted == 1; result.InfoSizeUncompressed = reader.ReadU32LE(); if (result.FileVersion >= 7 && filesFormat == 2) { result.IsInfoCompressed = true; result.InfoSizeCompressed = reader.ReadU32LE(); } else { result.InfoSizeCompressed = result.InfoSizeUncompressed; } result.InfoOffset = reader.Position; result.VirtualBlocksOffset = reader.Position + result.InfoSizeUncompressed; ulong blocksOffset = reader.Position + result.InfoSizeCompressed; for (var i = 0; i < blockCount; i++) { result.BlockOffsets.Add(blockOffsets[i] + blocksOffset); } return(result); }