コード例 #1
0
        /// <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);
        }