/// <summary> /// Apply a patch entry. /// </summary> /// <param name="header">Patch entry header.</param> /// <param name="data">Patch entry data.</param> static void ApplyPatchEntry(PatchHeader header, byte[] data) { var address = (int)header.Address; var index = (int)header.Index; if (header.Command == PatchCommand.NewFile) { var newFile = new MMFile { Addr = address, IsCompressed = false, Data = data, End = address + data.Length, IsStatic = header.Flags.HasFlag(PatchFlags.IsStatic), }; RomUtils.AppendFile(newFile); } else if (header.Command == PatchCommand.ExistingFile) { RomUtils.CheckCompressed(index); var original = RomData.MMFileList[index]; original.Data = VcDiffDecodeManaged(original.Data, data); if (original.Data.Length == 0) { original.Cmp_Addr = -1; original.Cmp_End = -1; } } }
/// <summary> /// Create patch data from current ROM state and write to <see cref="Stream"/>. /// </summary> /// <param name="outStream">Output stream.</param> /// <param name="originalMMFiles">Original <see cref="MMFile"/> collection.</param> /// <returns><see cref="SHA256"/> hash of the patch.</returns> public static byte[] CreatePatch(Stream outStream, List <MMFile> originalMMFiles) { var aes = Aes.Create(); var hashAlg = new SHA256Managed(); using (var cryptoStream = new CryptoStream(outStream, aes.CreateEncryptor(key, iv), CryptoStreamMode.Write)) using (var hashStream = new CryptoStream(cryptoStream, hashAlg, CryptoStreamMode.Write)) using (var compressStream = new GZipStream(hashStream, CompressionMode.Compress)) using (var writer = new BeBinaryWriter(compressStream)) { // Write magic value. writer.WriteUInt32(PatchMagic); Span <byte> headerBytes = stackalloc byte[PatchHeader.Size]; for (var fileIndex = 0; fileIndex < RomData.MMFileList.Count; fileIndex++) { var file = RomData.MMFileList[fileIndex]; // Check whether file should be included in the patch. if (file.Data == null || (file.IsCompressed && !file.WasEdited)) { continue; } if (fileIndex >= originalMMFiles.Count) { var index = (uint)fileIndex; var address = (uint)file.Addr; // Create header for appending new file. var header = PatchHeader.CreateNew(index, address, file.Data.Length, file.IsStatic); header.Write(headerBytes); // Write header bytes and file contents. writer.Write(headerBytes); writer.Write(file.Data); } else { RomUtils.CheckCompressed(fileIndex, originalMMFiles); var originalFile = originalMMFiles[fileIndex]; var index = (uint)fileIndex; var address = (uint)file.Addr; var diff = VcDiffEncodeManaged(originalFile.Data, file.Data); // Create header for patching existing file. var header = PatchHeader.CreateExisting(index, address, diff.Length, file.IsStatic); header.Write(headerBytes); // Write header bytes and diff bytes. writer.Write(headerBytes); writer.Write(diff); } } } return(hashAlg.Hash); }
/// <summary> /// Apply patch data from given <see cref="Stream"/> to the ROM. /// </summary> /// <param name="inStream">Input stream.</param> /// <returns><see cref="SHA256"/> hash of the patch.</returns> public static byte[] ApplyPatch(Stream inStream) { try { var aes = Aes.Create(); var hashAlg = new SHA256Managed(); using (var cryptoStream = new CryptoStream(inStream, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read)) using (var hashStream = new CryptoStream(cryptoStream, hashAlg, CryptoStreamMode.Read)) using (var decompressStream = new GZipStream(hashStream, CompressionMode.Decompress)) using (var memoryStream = new MemoryStream()) { // Fully decompress into MemoryStream so that we can access Position to check for end of Stream. decompressStream.CopyTo(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); using var reader = new BeBinaryReader(memoryStream); // Validate patch magic. var magic = reader.ReadUInt32(); ValidateMagic(magic); Span <byte> headerBytes = stackalloc byte[PatchHeader.Size]; while (reader.BaseStream.Position != reader.BaseStream.Length) { // Read header bytes into stack buffer to prevent allocation. reader.ReadExact(headerBytes); var header = PatchHeader.Read(headerBytes); var data = reader.ReadBytes(header.Length); ApplyPatchEntry(header, data); } } return(hashAlg.Hash); } catch { throw new IOException("Failed to apply patch. Patch may be invalid."); } }