mc-anvil
Version:
A Typescript library for reading Minecraft Anvil format files and Minecraft NBT format files in the browser.
160 lines • 6.42 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnvilParser = void 0;
const pako_1 = require("pako");
const _1 = require(".");
const __1 = require("..");
const util_1 = require("../util");
const chunk_1 = require("./chunk/chunk");
const types_1 = require("./types");
const LOCATION_ENTRIES_PER_FILE = 1024;
const LOCATION_ENTRY_SIZE = 4;
const SECTOR_SIZE = 4096;
class AnvilParser extends util_1.ResizableBinaryWriter {
constructor() {
super(...arguments);
this.dirtyChunks = new Map();
}
static chunkOffset(x, z) {
return 4 * (chunk_1.mod(x, 32) + chunk_1.mod(z, 32) * 32);
}
getValidMatchingChunkAtOffset(offset, valid) {
if (this.dirtyChunks.get(offset) && valid(this.dirtyChunks.get(offset)))
return this.dirtyChunks.get(offset);
this.seek(offset);
const entry = this.getLocationEntry();
if (entry.sectorCount === 0)
return;
const chunk = new _1.Chunk(new __1.NBTParser(this.getChunkData(entry.offset)).getTag());
if (valid(chunk)) {
this.dirtyChunks.set(offset, chunk);
return chunk;
}
}
buffer() {
this.setChunks();
return super.buffer();
}
getBlock(c) {
const chunk = this.getChunkContainingCoordinate(c);
if (!chunk)
throw new Error(`This region does not contain (${c[0]},${c[1]},${c[2]})`);
return chunk.getBlock(c);
}
setBlock(c, name, properties) {
const chunk = this.getChunkContainingCoordinate(c);
if (!chunk)
throw new Error(`This region does not contain (${c[0]},${c[1]},${c[2]})`);
chunk.setBlock(c, name, properties);
}
getChunkAtChunkCoordinates(x, z) {
return this.getValidMatchingChunkAtOffset(AnvilParser.chunkOffset(x, z), chunk => {
const coordinates = chunk.getChunkCoordinates();
return !coordinates || coordinates[0] !== x || coordinates[1] !== z;
});
}
getChunkContainingCoordinate(c) {
return this.getValidMatchingChunkAtOffset(AnvilParser.chunkOffset(Math.floor(c[0] / 16), Math.floor(c[2] / 16)), chunk => (chunk.containsCoordinate(c)));
}
getLocationEntry() {
return {
offset: this.getNByteInteger(3),
sectorCount: this.getByte()
};
}
setLocationEntry(entry) {
this.setNByteInteger(entry.offset, 3);
this.setByte(entry.sectorCount);
}
getLocationEntries() {
this.position = 0;
const r = [];
for (let i = 0; i < LOCATION_ENTRIES_PER_FILE; ++i)
r.push(this.getLocationEntry());
return r;
}
setLocationEntries(entries) {
entries.forEach(this.setLocationEntry.bind(this));
}
getTimestamps() {
this.position = LOCATION_ENTRIES_PER_FILE * LOCATION_ENTRY_SIZE;
const r = [];
for (let i = 0; i < LOCATION_ENTRIES_PER_FILE; ++i)
r.push(this.getUInt());
return r;
}
setTimestamps(value) {
this.position = LOCATION_ENTRIES_PER_FILE * LOCATION_ENTRY_SIZE;
value.forEach(this.setUInt.bind(this));
}
getChunkDataDescriptor(offset) {
if (offset !== undefined)
this.position = offset;
return {
length: this.getUInt(),
compressionType: this.getByte()
};
}
setChunkDataDescriptor(value, offset) {
if (offset !== undefined)
this.position = offset;
this.setUInt(value.length);
this.setByte(value.compressionType);
}
getChunkData(offset) {
if (offset !== undefined)
this.position = offset * SECTOR_SIZE;
const descriptor = this.getChunkDataDescriptor();
const data = this.view.buffer.slice(this.position, this.position + descriptor.length - 1);
this.position += descriptor.length;
switch (descriptor.compressionType) {
case types_1.CompressionType.NONE:
return data;
case types_1.CompressionType.ZLIB:
case types_1.CompressionType.GZIP:
return pako_1.inflate(new Uint8Array(data)).buffer;
}
}
getAllChunks() {
const offsets = this.getLocationEntries().filter(x => x.sectorCount > 0);
return offsets.map(x => new _1.Chunk(new __1.NBTParser(this.getChunkData(x.offset)).getTag()));
}
setChunks(chunks = [], exact) {
const locations = new Map();
const lengths = new Map();
const timestamps = new Map();
const toOverwrite = new Set(chunks.map(chunk => chunk.coordinateKey()).filter(x => x));
const existingChunks = (exact ? [] : this.getAllChunks()).filter(x => x.coordinateKey() && !toOverwrite.has(x.coordinateKey()));
const dirtyChunks = [...this.dirtyChunks.keys()].map(k => this.dirtyChunks.get(k));
let currentLocation = 2;
this.getTimestamps();
[...chunks.filter(x => x.coordinateKey()), ...existingChunks, ...dirtyChunks].forEach(chunk => {
const fullBuffer = new __1.NBTParser(new ArrayBuffer(SECTOR_SIZE));
fullBuffer.setTag(chunk.chunkData());
const data = pako_1.deflate(new Uint8Array(fullBuffer.buffer()));
const length = Math.ceil((data.byteLength + 5) / SECTOR_SIZE);
const key = chunk.coordinateKey();
lengths.set(key, length);
locations.set(key, currentLocation);
timestamps.set(key, Math.floor(new Date().getTime() / 1000));
currentLocation += length;
this.setInt(data.byteLength + 1);
this.setByte(types_1.CompressionType.ZLIB);
this.setArrayBuffer(data);
if (length * SECTOR_SIZE - data.byteLength - 5 > 0)
this.setArrayBuffer(new ArrayBuffer(length * SECTOR_SIZE - data.byteLength - 5));
});
[...locations.keys()].forEach(k => {
const p = k.split(",");
const x = +p[0];
const z = +p[1];
this.seek(AnvilParser.chunkOffset(x, z));
this.setNByteInteger(locations.get(k), 3);
this.setByte(lengths.get(k));
this.seek(AnvilParser.chunkOffset(x, z) + SECTOR_SIZE);
this.setInt(timestamps.get(k));
});
}
}
exports.AnvilParser = AnvilParser;
//# sourceMappingURL=anvil.js.map