public async Task Read_ZeroOffset_DataRead() { var fs = TestHelpers.CreateDataTypesTestFileSystem(); using (var file = fs.File.OpenRead(TestHelpers.DataTypesTestFilePath)) { var data = new UInt32Type(0x0); await data.ReadAsync(file, 0, new NefsProgress()); Assert.Equal((UInt32)0x05060708, data.Value); Assert.Equal("0x5060708", data.ToString()); } }
/// <summary> /// Reads the header intro from an input stream. Returns a new stream that contains the /// header data. This stream must be disposed by the caller. If the header is encrypted, the /// header data is decrypted before being placed in the new stream. /// </summary> /// <param name="stream">The stream to read from.</param> /// <param name="offset">The offset to the header intro from the beginning of the stream.</param> /// <param name="p">Progress info.</param> /// <returns>The loaded header intro and the stream to use for the rest of the header.</returns> internal async Task <(NefsHeaderIntro Intro, Stream HeaderStream)> ReadHeaderIntroAsync( Stream stream, ulong offset, NefsProgress p) { // The decrypted stream will need to be disposed by the caller var decryptedStream = new MemoryStream(); NefsHeaderIntro intro; // Read magic number (first four bytes) stream.Seek((long)offset, SeekOrigin.Begin); var magicNum = new UInt32Type(0); await magicNum.ReadAsync(stream, offset, p); // Reset stream position stream.Seek((long)offset, SeekOrigin.Begin); // Check magic number if (magicNum.Value == NefsHeaderIntro.NefsMagicNumber) { // This is a non-encrypted NeFS header intro = new NefsHeaderIntro(); await FileData.ReadDataAsync(stream, offset, intro, NefsVersion.Version200, p); // Copy the entire header to the decrypted stream (nothing to decrypt) stream.Seek((long)offset, SeekOrigin.Begin); await stream.CopyPartialAsync(decryptedStream, intro.HeaderSize, p.CancellationToken); } else { // Magic number is incorrect, assume file is encrpyted Log.LogInformation("Header magic number mismatch, assuming header is encrypted."); // Encrypted headers: // - Headers are "encrypted" in a two-step process. RSA-1024. No padding is used. // - First 0x80 bytes are signed with an RSA private key (data -> decrypt -> // scrambled data). // - Must use an RSA 1024-bit public key to unscramble the data (scrambled data -> // encrypt -> data). // - For DiRT Rally 2 this public key is stored in the main executable. byte[] encryptedHeader = new byte[NefsHeaderIntro.Size + 1]; // TODO : Why the +1? await stream.ReadAsync(encryptedHeader, 0, (int)NefsHeaderIntro.Size, p.CancellationToken); encryptedHeader[NefsHeaderIntro.Size] = 0; // Use big integers instead of RSA since the c# implementation forces the use of padding. var n = new BigInteger(this.RsaPublicKey); var e = new BigInteger(this.RsaExponent); var m = new BigInteger(encryptedHeader); // Decrypt the header intro byte[] decrypted = BigInteger.ModPow(m, e, n).ToByteArray(); decryptedStream.Write(decrypted, 0, decrypted.Length); // Fill any leftover space with zeros if (decrypted.Length != NefsHeaderIntro.Size) { for (int i = 0; i < (NefsHeaderIntro.Size - decrypted.Length); i++) { decryptedStream.WriteByte(0); } } // Read header intro data from decrypted stream intro = new NefsHeaderIntro(isEncrpyted: true); await FileData.ReadDataAsync(decryptedStream, 0, intro, NefsVersion.Version200, p); // The rest of the header is encrypted using AES-256, decrypt using the key from the // header intro byte[] key = intro.GetAesKey(); var headerSize = intro.HeaderSize; // Decrypt the rest of the header using (var rijAlg = new RijndaelManaged()) { rijAlg.KeySize = 256; rijAlg.Key = key; rijAlg.Mode = CipherMode.ECB; rijAlg.BlockSize = 128; rijAlg.Padding = PaddingMode.Zeros; var decryptor = rijAlg.CreateDecryptor(); decryptedStream.Seek(0, SeekOrigin.End); // Decrypt the data - make sure to leave open the base stream using (var cryptoStream = new CryptoStream(stream, decryptor, CryptoStreamMode.Read, true)) { // Decrypt data from input stream and copy to the decrypted stream await cryptoStream.CopyPartialAsync(decryptedStream, headerSize, p.CancellationToken); } } } return(intro, decryptedStream); }