public ModelDelta Revert(IDataModel model) { var reverse = new ModelDelta { HasDataChange = HasDataChange }; foreach (var kvp in oldData) { var(index, data) = (kvp.Key, kvp.Value); reverse.oldData[index] = model.Count > index ? model[index] : (byte)0xFF; if (model.Count > index) { model[index] = data; } } foreach (var kvp in addedRuns) { reverse.removedRuns[kvp.Key] = kvp.Value; } foreach (var kvp in removedRuns) { reverse.addedRuns[kvp.Key] = kvp.Value; } foreach (var kvp in addedNames) { reverse.removedNames[kvp.Key] = kvp.Value; } foreach (var kvp in removedNames) { reverse.addedNames[kvp.Key] = kvp.Value; } foreach (var kvp in addedLists) { reverse.removedLists[kvp.Key] = kvp.Value; } foreach (var kvp in removedLists) { reverse.addedLists[kvp.Key] = kvp.Value; } foreach (var kvp in addedMatchedWords) { reverse.removedMatchedWords[kvp.Key] = kvp.Value; } foreach (var kvp in removedMatchedWords) { reverse.addedMatchedWords[kvp.Key] = kvp.Value; } foreach (var kvp in addedOffsetPointers) { reverse.removedOffsetPointers[kvp.Key] = kvp.Value; } foreach (var kvp in removedOffsetPointers) { reverse.addedOffsetPointers[kvp.Key] = kvp.Value; } foreach (var kvp in addedUnmappedPointers) { reverse.removedUnmappedPointers[kvp.Key] = kvp.Value; } foreach (var kvp in removedUnmappedPointers) { reverse.addedUnmappedPointers[kvp.Key] = kvp.Value; } foreach (var kvp in addedUnmappedConstants) { reverse.removedUnmappedConstants[kvp.Key] = kvp.Value; } foreach (var kvp in removedUnmappedConstants) { reverse.addedUnmappedConstants[kvp.Key] = kvp.Value; } if (oldDataLength != -1 || newDataLength != -1) { model.ExpandData(reverse, oldDataLength - 1); model.ContractData(reverse, oldDataLength - 1); } model.MassUpdateFromDelta(addedRuns, removedRuns, addedNames, removedNames, addedUnmappedPointers, removedUnmappedPointers, addedMatchedWords, removedMatchedWords, addedOffsetPointers, removedOffsetPointers, addedUnmappedConstants, removedUnmappedConstants, addedLists, removedLists); return(reverse); }
// return -1 if the header is wrong (UPS1) // return -2 if the source file CRC doesn't match // return -3 if the patch file CRC doesn't match // return -4 if the source file size doesn't match // return -5 if the result file CRC doesn't match // return -6 if the UPS content didn't finish the last chunk with exactly 12 bytes left // return -7 if trying to write past the end of the destination file // returns a positive integer, the address of the first change, if everything worked correctly public static int ApplyUPSPatch(IDataModel model, byte[] patch, Func <ModelDelta> tokenFactory, bool ignoreChecksums, out UpsPatchDirection direction) { // 4 byte header: "UPS1" // variable width source-size // variable width destination-size // 12 byte footer: 3 CRC32 checksums. Source file, destination file, patch file (CRC of everything except the last 4 bytes) direction = UpsPatchDirection.Fail; // check header var headerMatches = patch.Take(4).Select(b => (char)b).SequenceEqual("UPS1"); if (!headerMatches) { return(-1); } // check source CRC var currentCRC = CalcCRC32(model.RawData); var patchSourceFileCRC = patch.ReadMultiByteValue(patch.Length - 12, 4); var patchDestinationFileCRC = patch.ReadMultiByteValue(patch.Length - 8, 4); if (currentCRC == patchSourceFileCRC) { direction = UpsPatchDirection.SourceToDestination; } if (currentCRC == patchDestinationFileCRC) { direction = UpsPatchDirection.DestinationToSource; } if (direction == UpsPatchDirection.Fail && !ignoreChecksums) { return(-2); } if (direction == UpsPatchDirection.Fail) { direction = UpsPatchDirection.SourceToDestination; } // check patch CRC var patchWithoutCRC = new byte[patch.Length - 4]; Array.Copy(patch, patchWithoutCRC, patchWithoutCRC.Length); var patchCRC = CalcCRC32(patchWithoutCRC); if (patchCRC != patch.ReadMultiByteValue(patch.Length - 4, 4)) { return(-3); } // resize (bigger) int readIndex = 4, firstEdit = int.MaxValue; int sourceSize = ReadVariableWidthInteger(patch, ref readIndex); int destinationSize = ReadVariableWidthInteger(patch, ref readIndex); int writeLength = destinationSize; if (direction == UpsPatchDirection.DestinationToSource) { (sourceSize, destinationSize) = (destinationSize, sourceSize); } if (sourceSize != model.Count && !ignoreChecksums) { return(-4); } var token = tokenFactory.Invoke(); model.ExpandData(token, destinationSize - 1); token.ChangeData(model, sourceSize, new byte[Math.Max(0, destinationSize - sourceSize)]); token.ChangeData(model, destinationSize, new byte[Math.Max(0, sourceSize - destinationSize)]); // run algorithm firstEdit = RunUPSPatchAlgorithm(model, patch, token, writeLength, destinationSize, ref readIndex); if (firstEdit < 0) { return(firstEdit); } // resize (smaller) model.ContractData(token, destinationSize - 1); // check result CRC if (!ignoreChecksums) { var finalCRC = CalcCRC32(model.RawData); if (direction == UpsPatchDirection.SourceToDestination && finalCRC != patchDestinationFileCRC) { return(-5); } if (direction == UpsPatchDirection.DestinationToSource && finalCRC != patchSourceFileCRC) { return(-5); } } // check that the chunk ended cleanly if (direction == UpsPatchDirection.SourceToDestination && readIndex != patch.Length - 12) { return(-6); } return(firstEdit); }