#pragma once #include #include #include #include namespace nall { struct bpslinear { 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 }; filemap sourceFile; const uint8_t* sourceData; uint sourceSize; filemap targetFile; const uint8_t* targetData; uint targetSize; }; auto bpslinear::source(const uint8_t* data, uint size) -> void { sourceData = data; sourceSize = size; } auto bpslinear::target(const uint8_t* data, uint size) -> void { targetData = data; targetSize = size; } auto bpslinear::source(const string& filename) -> bool { if(sourceFile.open(filename, filemap::mode::read) == false) return false; source(sourceFile.data(), sourceFile.size()); return true; } auto bpslinear::target(const string& filename) -> bool { if(targetFile.open(filename, filemap::mode::read) == false) return false; target(targetFile.data(), targetFile.size()); return true; } auto bpslinear::create(const string& filename, const string& metadata) -> bool { file modifyFile; if(modifyFile.open(filename, file::mode::write) == false) return false; Hash::CRC32 modifyChecksum; uint 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--; } }; uint targetReadLength = 0; auto targetReadFlush = [&]() { if(targetReadLength) { encode(TargetRead | ((targetReadLength - 1) << 2)); uint offset = outputOffset - targetReadLength; while(targetReadLength) write(targetData[offset++]), targetReadLength--; } }; 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]); while(outputOffset < targetSize) { uint sourceLength = 0; for(uint n = 0; outputOffset + n < min(sourceSize, targetSize); n++) { if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break; sourceLength++; } uint rleLength = 0; for(uint n = 1; outputOffset + n < targetSize; n++) { if(targetData[outputOffset] != targetData[outputOffset + n]) break; rleLength++; } if(rleLength >= 4) { //write byte to repeat targetReadLength++; outputOffset++; targetReadFlush(); //copy starting from repetition byte encode(TargetCopy | ((rleLength - 1) << 2)); uint relativeOffset = (outputOffset - 1) - targetRelativeOffset; encode(relativeOffset << 1); outputOffset += rleLength; targetRelativeOffset = outputOffset - 1; } else if(sourceLength >= 4) { targetReadFlush(); encode(SourceRead | ((sourceLength - 1) << 2)); outputOffset += sourceLength; } else { targetReadLength += Granularity; outputOffset += Granularity; } } targetReadFlush(); uint32_t sourceChecksum = Hash::CRC32(sourceData, sourceSize).digest().hex(); for(uint n = 0; n < 32; n += 8) write(sourceChecksum >> 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; } }