UNPKG

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
"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