feat: add regenerateChunkFromSeed to SaveIntegrity
This commit is contained in:
1 parent
cd9eeeddc0
commit
0753a55c07
1 file changed
+156
@@ -159,3 +159,159 @@ int regeneratedSurfaceHeight(uint64_t seed, int worldX, int worldZ) {
|
||||
int fx = floorMod(worldX, chunkSize);
|
||||
int fz = floorMod(worldZ, chunkSize);
|
||||
|
||||
int h00 = latticeHeight(seed, gx, gz);
|
||||
int h10 = latticeHeight(seed, gx + 1, gz);
|
||||
int h01 = latticeHeight(seed, gx, gz + 1);
|
||||
int h11 = latticeHeight(seed, gx + 1, gz + 1);
|
||||
|
||||
int nearEdge = h00 * (chunkSize - fx) + h10 * fx;
|
||||
int farEdge = h01 * (chunkSize - fx) + h11 * fx;
|
||||
return (nearEdge * (chunkSize - fz) + farEdge * fz) / (chunkSize * chunkSize);
|
||||
}
|
||||
|
||||
Chunk regenerateChunkFromSeed(uint64_t seed, const Vec3i& coord) {
|
||||
Chunk chunk;
|
||||
int baseY = coord.y * chunkSize;
|
||||
for (int x = 0; x < chunkSize; ++x) {
|
||||
for (int z = 0; z < chunkSize; ++z) {
|
||||
int surface = regeneratedSurfaceHeight(seed, coord.x * chunkSize + x,
|
||||
coord.z * chunkSize + z);
|
||||
for (int y = 0; y < chunkSize; ++y) {
|
||||
int worldY = baseY + y;
|
||||
Block block;
|
||||
if (worldY > surface) {
|
||||
block = Block::Air;
|
||||
} else if (worldY == surface) {
|
||||
block = Block::Grass;
|
||||
} else if (worldY > surface - 4) {
|
||||
block = Block::Dirt;
|
||||
} else {
|
||||
block = Block::Stone;
|
||||
}
|
||||
chunk.set(x, y, z, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
IntegrityReport SaveScanner::scan(const ByteBuffer& buffer) const {
|
||||
return scan(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
IntegrityReport SaveScanner::scan(const uint8_t* data, std::size_t size) const {
|
||||
IntegrityReport report;
|
||||
report.bufferBytes = size;
|
||||
|
||||
SaveHeader header;
|
||||
if (!readSaveHeader(data, size, header)) {
|
||||
report.issues.push_back(makeIssue(
|
||||
IntegrityIssueKind::TruncatedBuffer, IssueSeverity::Fatal, size,
|
||||
"buffer holds " + decimal(static_cast<long long>(size)) +
|
||||
" bytes, too short for the " + decimal(static_cast<long long>(saveHeaderBytes)) +
|
||||
" byte header"));
|
||||
return report;
|
||||
}
|
||||
if (header.magic != saveMagic) {
|
||||
report.issues.push_back(makeIssue(IntegrityIssueKind::BadMagic, IssueSeverity::Fatal, 0,
|
||||
"magic is not a save container"));
|
||||
return report;
|
||||
}
|
||||
if (!isKnownSaveVersion(header.version)) {
|
||||
report.issues.push_back(
|
||||
makeIssue(IntegrityIssueKind::UnknownVersion, IssueSeverity::Fatal, 4,
|
||||
"format version " + decimal(static_cast<long long>(header.version)) +
|
||||
" is outside the supported range v1..v4"));
|
||||
return report;
|
||||
}
|
||||
if (header.sectorBytes == 0) {
|
||||
report.issues.push_back(makeIssue(IntegrityIssueKind::MalformedHeader, IssueSeverity::Fatal,
|
||||
20, "header declares a sector size of zero"));
|
||||
return report;
|
||||
}
|
||||
|
||||
report.headerReadable = true;
|
||||
report.version = static_cast<SaveVersion>(header.version);
|
||||
report.regionX = header.regionX;
|
||||
report.regionZ = header.regionZ;
|
||||
report.declaredChunks = header.chunkCount;
|
||||
|
||||
std::size_t tableEnd = saveHeaderBytes + saveTableBytes(header);
|
||||
std::size_t availableEntries = header.chunkCount;
|
||||
if (tableEnd > size) {
|
||||
availableEntries = size > saveHeaderBytes ? (size - saveHeaderBytes) / saveEntryBytes : 0;
|
||||
report.issues.push_back(makeIssue(
|
||||
IntegrityIssueKind::ChunkCountOverrun, IssueSeverity::Recoverable, 16,
|
||||
"header declares " + decimal(static_cast<long long>(header.chunkCount)) +
|
||||
" chunks but only " + decimal(static_cast<long long>(availableEntries)) +
|
||||
" table entries fit in the buffer"));
|
||||
}
|
||||
|
||||
SaveHeader readable = header;
|
||||
readable.chunkCount = static_cast<uint32_t>(availableEntries);
|
||||
std::vector<SaveTableEntry> entries;
|
||||
readSaveTable(data, size, readable, entries);
|
||||
report.tableEntries = entries.size();
|
||||
|
||||
if (versionHasChecksum(report.version) && tableEnd <= size) {
|
||||
uint32_t computed = fnv1a32(data + saveHeaderBytes, saveTableBytes(header));
|
||||
if (computed != header.tableChecksum) {
|
||||
report.issues.push_back(makeIssue(
|
||||
IntegrityIssueKind::ChecksumMismatch, IssueSeverity::Recoverable, 24,
|
||||
"entry table checksum is " + decimal(static_cast<long long>(header.tableChecksum)) +
|
||||
" but the table hashes to " + decimal(static_cast<long long>(computed))));
|
||||
}
|
||||
}
|
||||
|
||||
RegionCoord expectedRegion{header.regionX, header.regionZ};
|
||||
std::size_t headerSectors = saveHeaderSectors(readable);
|
||||
std::vector<SectorSpan> spans;
|
||||
std::size_t lastSectorEnd = headerSectors;
|
||||
|
||||
for (std::size_t i = 0; i < entries.size(); ++i) {
|
||||
const SaveTableEntry& entry = entries[i];
|
||||
std::size_t entryAt = saveHeaderBytes + i * saveEntryBytes;
|
||||
int index = static_cast<int>(i);
|
||||
|
||||
auto locate = [&](IntegrityIssue& issue) {
|
||||
issue.regionX = header.regionX;
|
||||
issue.regionZ = header.regionZ;
|
||||
issue.entryIndex = index;
|
||||
issue.hasChunkCoord = true;
|
||||
issue.chunkCoord = entry.coord;
|
||||
};
|
||||
|
||||
bool coordBroken = false;
|
||||
if (!(regionForChunk(entry.coord) == expectedRegion)) {
|
||||
RegionCoord actual = regionForChunk(entry.coord);
|
||||
IntegrityIssue issue = makeIssue(
|
||||
IntegrityIssueKind::ChunkOutsideRegion, IssueSeverity::Recoverable, entryAt,
|
||||
"chunk " + coordText(entry.coord) + " belongs to region (" + decimal(actual.x) +
|
||||
", " + decimal(actual.z) + "), not (" + decimal(header.regionX) + ", " +
|
||||
decimal(header.regionZ) + ")");
|
||||
locate(issue);
|
||||
report.issues.push_back(issue);
|
||||
coordBroken = true;
|
||||
}
|
||||
if (entry.coord.y < saveChunkYMin || entry.coord.y > saveChunkYMax) {
|
||||
IntegrityIssue issue = makeIssue(
|
||||
IntegrityIssueKind::ChunkHeightOutOfRange, IssueSeverity::Recoverable, entryAt + 4,
|
||||
"chunk Y " + decimal(entry.coord.y) + " is outside the world range " +
|
||||
decimal(saveChunkYMin) + ".." + decimal(saveChunkYMax));
|
||||
locate(issue);
|
||||
report.issues.push_back(issue);
|
||||
coordBroken = true;
|
||||
}
|
||||
|
||||
std::size_t payloadAt = static_cast<std::size_t>(entry.sectorOffset) * header.sectorBytes;
|
||||
if (entry.sectorCount == 0 || payloadAt >= size) {
|
||||
IntegrityIssue issue = makeIssue(
|
||||
IntegrityIssueKind::OrphanEntry, IssueSeverity::Recoverable, entryAt + 12,
|
||||
"entry for chunk " + coordText(entry.coord) + " points at sector " +
|
||||
decimal(entry.sectorOffset) + " spanning " + decimal(entry.sectorCount) +
|
||||
" sectors, which holds no payload");
|
||||
locate(issue);
|
||||
report.issues.push_back(issue);
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user