mirror of https://github.com/bsnes-emu/bsnes.git
149 lines
3.9 KiB
C++
149 lines
3.9 KiB
C++
#pragma once
|
|
|
|
#include <nall/file.hpp>
|
|
#include <nall/filemap.hpp>
|
|
#include <nall/stdint.hpp>
|
|
#include <nall/string.hpp>
|
|
|
|
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;
|
|
}
|
|
|
|
}
|