private void LoadEntry(int id) { if (id < 0 || id > _files.Count - 1) { throw new Exception("Tried to read CRAF entry out of bounds"); } if (_inputStream is null) { throw new Exception("Tried to load CRAF entry after reader was closed"); } using (BinaryReader bin = new BinaryReader(_inputStream, Encoding.Default, true)) { _inputStream.Seek(_files[id].dataOffset, SeekOrigin.Begin); if (_files[id].UseCompression) { var chunkCount = ChunkCount(_files[id].uncompressedSize); _files[id].chunks = new CrafChunk[chunkCount]; for (var j = 0; j < chunkCount; j++) { _files[id].chunks[j] = new CrafChunk(); var compressedSize = bin.ReadInt32(); _files[id].chunks[j].uncompressedSize = bin.ReadInt32(); if (MetadataEncrypted && j == 0) { Int64 chunkKey = (MasterChunkKey1 * _files[id].chunkKey) + MasterChunkKey2; int compressedSizeKey = (int)(chunkKey >> 32); int uncompressedSizeKey = (int)(chunkKey & 0xFFFFFFFF); compressedSize ^= compressedSizeKey; _files[id].chunks[j].uncompressedSize ^= uncompressedSizeKey; } _files[id].chunks[j].compressedData = bin.ReadBytes(compressedSize); } } else if (_files[id].UseDataEncryption) { var decryptor = _aes.CreateDecryptor(AESKey, _files[id].IV); using (var decryptorStream = new ReuseCryptoStream(_inputStream, decryptor, CryptoStreamMode.Read)) { _files[id].data = new byte[_files[id].uncompressedSize]; decryptorStream.Read(_files[id].data, 0, _files[id].uncompressedSize); } } else { _files[id].data = bin.ReadBytes(_files[id].uncompressedSize); } } }
public Task SaveAsync(string Path, IProgress <int> progress) { if (!_loaded) { throw new Exception("Tried to save CRAF before loading data"); } if (_inputStream != null) { throw new Exception("Tried to save CRAF while still in read mode"); } return(Task.Run(() => { // Create mode is slow if (File.Exists(Path)) { File.Delete(Path); } using (FileStream file = File.OpenWrite(Path)) { using (BinaryWriter bin = new BinaryWriter(file)) { var magic = new char[] { 'C', 'R', 'A', 'F' }; bin.Write(magic); bin.Write(_versionMinor); bin.Write(_versionMajor); bin.Write(_metadataEncryption); bin.Write(_files.Count); bin.Write(_unk0); file.Seek(0x20, SeekOrigin.Begin); bin.Write(_flags); bin.Write(_unk1); bin.Write(_archiveKey); bin.Write(_unk2); uint firstEntryOffset = (uint)file.Position; file.Seek(firstEntryOffset + 0x28 * _files.Count, SeekOrigin.Begin); file.Seek(AlignTo(file.Position, 16), SeekOrigin.Begin); // don't ask me why, but it's normally padded like that file.Seek(8, SeekOrigin.Current); for (var i = 0; i < _files.Count; i++) { var vfsPathOffset = AlignTo(file.Position, 8); file.Seek(vfsPathOffset, SeekOrigin.Begin); bin.WriteNullTerminatedString(_files[i].vfsPath); _files[i].vfsPathOffset = (uint)vfsPathOffset; } // don't ask me why, but it's normally padded like that file.Seek(AlignTo(file.Position, 16), SeekOrigin.Begin); file.Seek(8, SeekOrigin.Current); for (var i = 0; i < _files.Count; i++) { var pathOffset = AlignTo(file.Position, 8); file.Seek(pathOffset, SeekOrigin.Begin); bin.WriteNullTerminatedString(_files[i].path); _files[i].pathOffset = (uint)pathOffset; } for (var i = 0; i < _files.Count; i++) { var dataOffset = AlignTo(file.Position, 0x200); file.Seek(dataOffset, SeekOrigin.Begin); if (_files[i].UseCompression) { for (var j = 0; j < _files[i].chunks.Length; j++) { var compressedSize = _files[i].chunks[j].compressedData.Length; var uncompressedSize = _files[i].chunks[j].uncompressedSize; if (MetadataEncrypted && j == 0) { Int64 chunkKey = (MasterChunkKey1 * _files[i].chunkKey) + MasterChunkKey2; int compressedSizeKey = (int)(chunkKey >> 32); int uncompressedSizeKey = (int)(chunkKey & 0xFFFFFFFF); compressedSize ^= compressedSizeKey; uncompressedSize ^= uncompressedSizeKey; } bin.Write(compressedSize); bin.Write(uncompressedSize); bin.Write(_files[i].chunks[j].compressedData); } file.Seek(dataOffset + _files[i].totalCompressedSize, SeekOrigin.Begin); // compressed files are padded; } else if (_files[i].UseDataEncryption) { var encryptor = _aes.CreateEncryptor(AESKey, _files[i].IV); using (var encryptorStream = new ReuseCryptoStream(file, encryptor, CryptoStreamMode.Write)) { encryptorStream.Write(_files[i].data, 0, _files[i].data.Length); } bin.Write(_files[i].IV); for (var j = 0; j < 16; j++) { bin.Write((byte)0x00); } bin.Write((byte)0x01); } else { bin.Write(_files[i].data); } _files[i].dataOffset = (uint)dataOffset; // files should not directly follow one another file.Seek(1, SeekOrigin.Current); progress.Report(i + 1); } // original files are padded at the end if (AlignTo(file.Position, 0x200) != file.Position) { file.Seek(AlignTo(file.Position, 0x200) - 1, SeekOrigin.Begin); // force filesize increase file.WriteByte(0); } file.Seek(0x10, SeekOrigin.Begin); bin.Write(firstEntryOffset); bin.Write(_files[0].vfsPathOffset); bin.Write(_files[0].pathOffset); bin.Write((int)(_files[0].dataOffset)); file.Seek(firstEntryOffset, SeekOrigin.Begin); Int64 rollingKey = MasterArchiveKey ^ _archiveKey; for (var i = 0; i < _files.Count; i++) { var uncompressedSize = _files[i].uncompressedSize; var totalCompressedSize = _files[i].totalCompressedSize; var dataOffset = _files[i].dataOffset; var entryKey = _files[i].entryKey; if (MetadataEncrypted) { Int64 fileSizeKey = (rollingKey * MasterEntryKey) ^ entryKey; int uncompressedSizeKey = (int)(fileSizeKey >> 32); int compressedSizeKey = (int)(fileSizeKey & 0xFFFFFFFF); uncompressedSize ^= uncompressedSizeKey; totalCompressedSize ^= compressedSizeKey; Int64 dataOffsetKey = (fileSizeKey * MasterEntryKey) ^ ~(entryKey); dataOffset ^= dataOffsetKey; rollingKey = dataOffsetKey; } bin.Write(entryKey); bin.Write(uncompressedSize); bin.Write(totalCompressedSize); bin.Write(_files[i].flags); bin.Write(_files[i].vfsPathOffset); bin.Write(dataOffset); bin.Write(_files[i].pathOffset); bin.Write(_files[i].unk0); bin.Write(_files[i].chunkKey); } } } })); }