#pragma once #include #include #include #include #include namespace nall { struct bpsdelta { inline auto source(const uint8_t* data, uint size) -> void; inline auto target(const uint8_t* data, uint size) -> void; inline auto source(const string& filename) -> bool; inline auto target(const string& filename) -> bool; inline auto create(const string& filename, const string& metadata = "") -> bool; protected: enum : uint { SourceRead, TargetRead, SourceCopy, TargetCopy }; enum : uint { Granularity = 1 }; struct Node { Node() = default; ~Node() { if(next) delete next; } uint offset = 0; Node* next = nullptr; }; filemap sourceFile; const uint8_t* sourceData; uint sourceSize; filemap targetFile; const uint8_t* targetData; uint targetSize; }; auto bpsdelta::source(const uint8_t* data, uint size) -> void { sourceData = data; sourceSize = size; } auto bpsdelta::target(const uint8_t* data, uint size) -> void { targetData = data; targetSize = size; } auto bpsdelta::source(const string& filename) -> bool { if(sourceFile.open(filename, filemap::mode::read) == false) return false; source(sourceFile.data(), sourceFile.size()); return true; } auto bpsdelta::target(const string& filename) -> bool { if(targetFile.open(filename, filemap::mode::read) == false) return false; target(targetFile.data(), targetFile.size()); return true; } auto bpsdelta::create(const string& filename, const string& metadata) -> bool { file modifyFile; if(modifyFile.open(filename, file::mode::write) == false) return false; Hash::CRC32 sourceChecksum, modifyChecksum; uint sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0; auto write = [&](uint8_t data) { modifyFile.write(data); modifyChecksum.input(data); }; auto encode = [&](uint64_t data) { while(true) { uint64_t x = data & 0x7f; data >>= 7; if(data == 0) { write(0x80 | x); break; } write(x); data--; } }; write('B'); write('P'); write('S'); write('1'); encode(sourceSize); encode(targetSize); uint markupSize = metadata.length(); encode(markupSize); for(uint n = 0; n < markupSize; n++) write(metadata[n]); Node* sourceTree[65536]; Node* targetTree[65536]; for(uint n = 0; n < 65536; n++) sourceTree[n] = nullptr, targetTree[n] = nullptr; //source tree creation for(uint offset = 0; offset < sourceSize; offset++) { uint16_t symbol = sourceData[offset + 0]; sourceChecksum.input(symbol); if(offset < sourceSize - 1) symbol |= sourceData[offset + 1] << 8; Node *node = new Node; node->offset = offset; node->next = sourceTree[symbol]; sourceTree[symbol] = node; } uint targetReadLength = 0; auto targetReadFlush = [&]() { if(targetReadLength) { encode(TargetRead | ((targetReadLength - 1) << 2)); uint offset = outputOffset - targetReadLength; while(targetReadLength) write(targetData[offset++]), targetReadLength--; } }; while(outputOffset < targetSize) { uint maxLength = 0, maxOffset = 0, mode = TargetRead; uint16_t symbol = targetData[outputOffset + 0]; if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8; { //source read uint length = 0, offset = outputOffset; while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) { length++; offset++; } if(length > maxLength) maxLength = length, mode = SourceRead; } { //source copy Node* node = sourceTree[symbol]; while(node) { uint length = 0, x = node->offset, y = outputOffset; while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++; if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = SourceCopy; node = node->next; } } { //target copy Node* node = targetTree[symbol]; while(node) { uint length = 0, x = node->offset, y = outputOffset; while(y < targetSize && targetData[x++] == targetData[y++]) length++; if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = TargetCopy; node = node->next; } //target tree append node = new Node; node->offset = outputOffset; node->next = targetTree[symbol]; targetTree[symbol] = node; } { //target read if(maxLength < 4) { maxLength = min((uint)Granularity, targetSize - outputOffset); mode = TargetRead; } } if(mode != TargetRead) targetReadFlush(); switch(mode) { case SourceRead: encode(SourceRead | ((maxLength - 1) << 2)); break; case TargetRead: //delay write to group sequential TargetRead commands into one targetReadLength += maxLength; break; case SourceCopy: case TargetCopy: encode(mode | ((maxLength - 1) << 2)); int relativeOffset; if(mode == SourceCopy) { relativeOffset = maxOffset - sourceRelativeOffset; sourceRelativeOffset = maxOffset + maxLength; } else { relativeOffset = maxOffset - targetRelativeOffset; targetRelativeOffset = maxOffset + maxLength; } encode((relativeOffset < 0) | (abs(relativeOffset) << 1)); break; } outputOffset += maxLength; } targetReadFlush(); for(uint n = 0; n < 32; n += 8) write(sourceChecksum.digest().hex() >> n); uint32_t targetChecksum = Hash::CRC32(targetData, targetSize).digest().hex(); for(uint n = 0; n < 32; n += 8) write(targetChecksum >> n); uint32_t outputChecksum = modifyChecksum.digest().hex(); for(uint n = 0; n < 32; n += 8) write(outputChecksum >> n); modifyFile.close(); return true; } }