public PatchApplyResponse Apply(Stream oldVersion, Stream patchStream, Stream output, IPatchProgress prog) { // Checksum our input to make sure things are okay byte[] oldVersionHash = MD5.Check(oldVersion); oldVersion.Seek(0, SeekOrigin.Begin); // Check if our signature is the same as the output bool isRequired = false; for (int i = 0; i < 16; i++) { if (oldVersionHash[i] != mPatFileInfo.TargetChecksum[i]) { isRequired = true; break; } } if (!isRequired) { return PatchApplyResponse.NotRequired; } // Make sure our file signatures match up for (int i = 0; i < 16; i++) { if (oldVersionHash[i] != mPatFileInfo.SourceChecksum[i]) return PatchApplyResponse.WrongFile; } byte[] copyBuffer = new byte[4096]; BinaryReader br = new BinaryReader(patchStream); for (int currentBlock = 0; currentBlock < (int)mPatFileInfo.BlockCount; currentBlock++) { ulong blockSize = 0; long derp = patchStream.Position; byte blockType = br.ReadByte(); switch (blockType) { // Identical blocks // ================ // Copy an amount of data from the original file in to the new one. case 1: case 2: case 3: // Decode the block length switch (blockType) { case 1: blockSize = (ulong)br.ReadByte(); break; case 2: blockSize = (ulong)br.ReadUInt16(); break; case 3: blockSize = (ulong)br.ReadUInt32(); break; } long sourceOffset = br.ReadUInt32(); oldVersion.Seek(sourceOffset, SeekOrigin.Begin); // If we have a derpyblock or couldn't read it, count it as a failure. if (blockSize < 1) { return PatchApplyResponse.Failed; } // Copy from the source to the output. while (blockSize > 0) { int read = oldVersion.Read(copyBuffer, 0, (int)Math.Min(4096, blockSize)); if (read <= 0) { throw new IOException(); } output.Write(copyBuffer, 0, read); blockSize -= (ulong)read; } break; // Payload delivery blocks // ======================= // Copy an amount of data from our patch file in to the new one. case 5: case 6: case 7: switch (blockType) { case 5: blockSize = (ulong)br.ReadByte(); break; case 6: blockSize = (ulong)br.ReadUInt16(); break; case 7: blockSize = (ulong)br.ReadUInt32(); break; } while (blockSize > 0) { int read = br.Read(copyBuffer, 0, (int)Math.Min(4096, blockSize)); if (read <= 0) { throw new IOException(); } output.Write(copyBuffer, 0, read); blockSize -= (ulong)read; } break; // Its the end of the taco stand, taco taco stand. case 255: // TODO: Should we really care about the timestamp? br.ReadInt64(); break; default: return PatchApplyResponse.Failed; } // Issue any progress updates before moving to the next block if (prog != null) { prog.OnPatchProgress(currentBlock, mPatFileInfo.BlockCount); } } // Make sure we applied the patch correctly output.Seek(0, SeekOrigin.Begin); byte[] patchedFileChecksum = MD5.Check(output); for (int i = 0; i < 16; i++) { if (patchedFileChecksum[i] != mPatFileInfo.TargetChecksum[i]) return PatchApplyResponse.Failed; } // We're done! return PatchApplyResponse.Ok; }
/// <param name="sameBlocks"> /// This list will store blocks that have been found to have remained /// the same between files. /// </param> public void Execute(IList<SameBlock> sameBlocks, IPatchProgress prog) { if (sameBlocks == null) throw new ArgumentNullException(); ChunkedFile sourceTree = new ChunkedFile(mSource, mSourceSize, mBlockSize); // the vector needs an 'empty' first block so checking for overlap with the 'previous' block never fails. sameBlocks.Add(new SameBlock()); mTargetCDataBaseOffset = 0; mTargetCDataSize = 0; bool firstRun = true; // currentOffset is in the target file for (long currentOffset = 0; currentOffset < mTargetSize;) { bool reloadTargetCData = true; if ((currentOffset >= mTargetCDataBaseOffset) && (currentOffset + TargetLookaheadSize < mTargetCDataBaseOffset + TargetBufferSize)) { if (firstRun) { firstRun = false; } else { reloadTargetCData = false; } } if (reloadTargetCData) { // at least support looking back blockSize, if possible (findBlock relies on this!) mTargetCDataBaseOffset = currentOffset - mBlockSize; // handle start of file correctly if (currentOffset < BlockSize) mTargetCDataBaseOffset = 0; mTargetCDataSize = TargetBufferSize; // check if this does not extend beyond EOF if (mTargetCDataBaseOffset + mTargetCDataSize > mTargetSize) { mTargetCDataSize = mTargetSize - mTargetCDataBaseOffset; } // we need to update the memory cache of target // TODO: Emit debug info here, if verbose is enabled. // cout << "[CacheReload] File position = " << static_cast<unsigned long>(targetCDataBaseOffset) << "\n"; if (prog != null) { prog.OnPatchProgress(mTargetCDataBaseOffset, mTargetSize); } mTarget.Seek(mTargetCDataBaseOffset, SeekOrigin.Begin); mTarget.Read(mTargetCData, 0, (int)mTargetCDataSize); } SameBlock currentSameBlock = FindBlock(sourceTree, currentOffset); if (currentSameBlock != null) { // We have a match. SameBlock previousBlock = sameBlocks[sameBlocks.Count-1]; if (previousBlock.TargetOffset + previousBlock.Size > currentSameBlock.TargetOffset) { // There is overlap, resolve it. long difference = previousBlock.TargetOffset + previousBlock.Size - currentSameBlock.TargetOffset; currentSameBlock.SourceOffset += difference; currentSameBlock.TargetOffset += difference; currentSameBlock.Size -= difference; } Console.WriteLine(currentSameBlock.ToString()); sameBlocks.Add(currentSameBlock); // TODO: Emit debug info here, if verbose is enabled. currentOffset = currentSameBlock.TargetOffset + currentSameBlock.Size; } else { // No match, advance to the next byte. currentOffset++; } } // Add a block at the end to prevent bounds checking hassles. SameBlock lastBlock = new SameBlock(); lastBlock.SourceOffset = 0; lastBlock.TargetOffset = mTargetSize; lastBlock.Size = 0; sameBlocks.Add(lastBlock); }