/// <returns>The first offset that was edited</returns> public static int ApplyIPSPatch(IDataModel model, byte[] patch, ModelDelta token) { // 5 byte header (PATCH) and 3 byte footer (EOF) // hunk type 1: offset (3 bytes), length (2 bytes), payload (length bytes). Write the payload at offset. // RLE hunk: offset (3 bytes), 00 00, length (2 bytes), target (1 byte). Write the target, length times, at offset var start = 5; var firstOffset = -1; while (patch.Length - start >= 6) { var offset = (patch[start] << 16) + (patch[start + 1] << 8) + patch[start + 2]; if (firstOffset < 0) { firstOffset = offset; } start += 3; var length = (patch[start] << 8) + patch[start + 1]; start += 2; if (length > 0) { // normal model.ExpandData(token, offset + length - 1); while (length > 0) { token.ChangeData(model, offset, patch[start]); offset += 1; start += 1; length -= 1; } } else { length = (patch[start] << 8) + patch[start + 1]; start += 2; model.ExpandData(token, offset + length - 1); // rle while (length > 0) { token.ChangeData(model, offset, patch[start]); offset += 1; length -= 1; } start += 1; } } return(firstOffset); }
public virtual bool ChangeData(IDataModel model, int index, byte data) { if (model.Count > index && model[index] == data) { return(false); } if (!oldData.ContainsKey(index)) { if (model.Count <= index) { model.ExpandData(this, index); } oldData[index] = model[index]; } var valueChanged = model[index] != data; model[index] = data; if (!HasDataChange) { HasDataChange = true; OnNewChange?.Invoke(this, EventArgs.Empty); } return(valueChanged); }
public virtual void ChangeData(IDataModel model, int index, byte data) { if (!oldData.ContainsKey(index)) { if (model.Count <= index) { model.ExpandData(this, index); } oldData[index] = model[index]; } model[index] = data; if (!HasDataChange) { HasDataChange = true; OnNewDataChange?.Invoke(this, EventArgs.Empty); } }
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); }