mcbe-leveldb 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LevelUtils.js ADDED
@@ -0,0 +1,1168 @@
1
+ import { appendFileSync } from "node:fs";
2
+ import NBT from "prismarine-nbt";
3
+ import BiomeData from "./__biome_data__.js";
4
+ //#region Local Constants
5
+ const DEBUG = false;
6
+ const fileNameEncodeCharacterRegExp = /[/:>?\\]/g;
7
+ const fileNameCharacterFilterRegExp = /[^a-zA-Z0-9-_+,-.;=@~/:>?\\]/g;
8
+ function readInt16LE(buf, offset) {
9
+ const val = buf[offset] | (buf[offset + 1] << 8);
10
+ return val >= 0x8000 ? val - 0x10000 : val;
11
+ }
12
+ function writeInt16LE(value) {
13
+ const buf = Buffer.alloc(2);
14
+ buf.writeInt16LE(value, 0);
15
+ return buf;
16
+ }
17
+ function writeInt32LE(value) {
18
+ const buf = Buffer.alloc(4);
19
+ buf.writeInt32LE(value, 0);
20
+ return buf;
21
+ }
22
+ // export function readData3dValue(rawvalue: Uint8Array | null): {
23
+ // /**
24
+ // * A height map in the form [x][z].
25
+ // */
26
+ // heightMap: TupleOfLength16<TupleOfLength16<number>>;
27
+ // /**
28
+ // * A biome map in the form [x][y][z].
29
+ // */
30
+ // biomeMap: TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>>;
31
+ // } | null {
32
+ // if (!rawvalue) {
33
+ // return null;
34
+ // }
35
+ // // --- Height map (first 512 bytes -> 256 signed 16-bit ints) ---
36
+ // const heightMap: TupleOfLength16<TupleOfLength16<number>> = Array.from(
37
+ // { length: 16 },
38
+ // (): TupleOfLength16<number> => Array(16).fill(0) as TupleOfLength16<number>
39
+ // ) as TupleOfLength16<TupleOfLength16<number>>;
40
+ // for (let i: number = 0; i < 256; i++) {
41
+ // const val: number = readInt16LE(rawvalue, i * 2);
42
+ // const x: number = i % 16;
43
+ // const z: number = Math.floor(i / 16);
44
+ // heightMap[x]![z] = val;
45
+ // }
46
+ // // --- Biome subchunks (remaining bytes) ---
47
+ // const biomeBytes: Uint8Array<ArrayBuffer> = rawvalue.slice(512);
48
+ // let b: BiomePalette[] = readChunkBiomes(biomeBytes);
49
+ // // Validate Biome Data
50
+ // if (b.length === 0 || b[0]!.values === null) {
51
+ // throw new Error("Value does not contain at least one subchunk of biome data.");
52
+ // }
53
+ // // Enlarge list to length 24 if necessary
54
+ // if (b.length < 24) {
55
+ // while (b.length < 24) b.push({ values: null, palette: [] });
56
+ // }
57
+ // // Trim biome data
58
+ // if (b.length > 24) {
59
+ // if (b.slice(24).some((sub: BiomePalette): boolean => sub.values !== null)) {
60
+ // console.warn(`Trimming biome data from ${b.length} to 24 subchunks.`);
61
+ // }
62
+ // b = b.slice(0, 24);
63
+ // }
64
+ // // --- Fill biome_map [16][24*16][16] (x,y,z order) ---
65
+ // const biomeMap: TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>> = Array.from(
66
+ // { length: 16 },
67
+ // (): TupleOfLength16<TupleOfLength16<number>> =>
68
+ // Array.from({ length: 24 * 16 }, (): TupleOfLength16<number> => Array(16).fill(NaN) as TupleOfLength16<number>) as TupleOfLength16<
69
+ // TupleOfLength16<number>
70
+ // >
71
+ // ) as TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>>;
72
+ // const hasData: boolean[] = b.map((sub: BiomePalette): boolean => sub.values !== null);
73
+ // const ii: number[] = hasData
74
+ // .map((has: boolean, idx: number): number | null => (has ? idx + 1 : null))
75
+ // .filter((i: number | null): i is number => i !== null);
76
+ // for (const i of ii) {
77
+ // const bb: BiomePalette = b[i - 1]!;
78
+ // if (!bb.values) continue;
79
+ // for (let u: number = 0; u < 4096; u++) {
80
+ // const val: number = bb.palette[bb.values[u]! - 1]!; // R is 1-based
81
+ // const x: number = u % 16;
82
+ // const z: number = Math.floor(u / 16) % 16;
83
+ // const y: number = Math.floor(u / 256);
84
+ // biomeMap[x]![16 * (i - 1) + y]![z] = val;
85
+ // }
86
+ // }
87
+ // // Fill missing subchunks by copying top of last data subchunk
88
+ // const iMax: number = Math.max(...ii);
89
+ // if (iMax < 24) {
90
+ // const y: number = 16 * iMax - 1;
91
+ // for (let yy: number = y + 1; yy < 24 * 16; yy++) {
92
+ // for (let x: number = 0; x < 16; x++) {
93
+ // for (let z: number = 0; z < 16; z++) {
94
+ // biomeMap[x]![yy]![z] = biomeMap[x]![y]![z]!;
95
+ // }
96
+ // }
97
+ // }
98
+ // }
99
+ // return { heightMap, biomeMap };
100
+ // }
101
+ export function readData3dValue(rawvalue) {
102
+ if (!rawvalue) {
103
+ return null;
104
+ }
105
+ // --- Height map (first 512 bytes -> 256 signed 16-bit ints) ---
106
+ const heightMap = Array.from({ length: 16 }, () => Array(16).fill(0));
107
+ for (let i = 0; i < 256; i++) {
108
+ const val = readInt16LE(rawvalue, i * 2);
109
+ const x = i % 16;
110
+ const z = Math.floor(i / 16);
111
+ heightMap[x][z] = val;
112
+ }
113
+ // --- Biome subchunks (remaining bytes) ---
114
+ const biomeBytes = rawvalue.slice(512);
115
+ let biomes = readChunkBiomes(biomeBytes);
116
+ // Validate Biome Data
117
+ if (biomes.length === 0 || biomes[0].values === null) {
118
+ throw new Error("Value does not contain at least one subchunk of biome data.");
119
+ }
120
+ // Enlarge list to length 24 if necessary
121
+ if (biomes.length < 24) {
122
+ while (biomes.length < 24) {
123
+ biomes.push({ values: null, palette: [] });
124
+ }
125
+ }
126
+ // Trim biome data (DISABLED (so that when increasing the height limits, it still works properly))
127
+ // if (biomeMap.length > 24) {
128
+ // if (biomeMap.slice(24).some((sub: BiomePalette): boolean => sub.values !== null)) {
129
+ // console.warn(`Trimming biome data from ${biomeMap.length} to 24 subchunks.`);
130
+ // }
131
+ // biomeMap = biomeMap.slice(0, 24);
132
+ // }
133
+ return { heightMap, biomes };
134
+ }
135
+ export function writeData3DValue(heightMap, biomes) {
136
+ // heightMap is 16x16, flatten to 256 shorts
137
+ const flatHeight = heightMap.flatMap((v, i) => v.map((_v2, i2) => heightMap[i2][i]));
138
+ // Write height map (512 bytes)
139
+ const heightBufs = flatHeight.map((v) => writeInt16LE(v));
140
+ const heightBuf = Buffer.concat(heightBufs);
141
+ // Write biome data
142
+ const biomeBuf = writeChunkBiomes(biomes);
143
+ // Combine
144
+ return Buffer.concat([heightBuf, biomeBuf]);
145
+ }
146
+ function readChunkBiomes(rawvalue) {
147
+ let p = 0;
148
+ const end = rawvalue.length;
149
+ const result = [];
150
+ while (p < end) {
151
+ const { values, isPersistent, paletteSize, newOffset } = readSubchunkPaletteIds(rawvalue, p, end);
152
+ p = newOffset;
153
+ if (values === null) {
154
+ result.push({ values: null, palette: [] });
155
+ continue;
156
+ }
157
+ if (isPersistent) {
158
+ throw new Error("Biome palette does not have runtime ids.", { cause: result.length });
159
+ }
160
+ if (end - p < paletteSize * 4) {
161
+ throw new Error("Subchunk biome palette is truncated.");
162
+ }
163
+ const palette = [];
164
+ for (let i = 0; i < paletteSize; i++) {
165
+ const val = rawvalue[p] | (rawvalue[p + 1] << 8) | (rawvalue[p + 2] << 16) | (rawvalue[p + 3] << 24);
166
+ palette.push(val);
167
+ p += 4;
168
+ }
169
+ result.push({ values, palette });
170
+ }
171
+ return result;
172
+ }
173
+ export function writeChunkBiomes(biomes) {
174
+ const buffers = [];
175
+ for (const bb of biomes) {
176
+ if (!bb || bb.values === null || bb.values.length === 0)
177
+ continue; // NULL case in R
178
+ const { values, palette } = bb;
179
+ // --- Write subchunk palette ids (bitpacked) ---
180
+ const idsBuf = writeSubchunkPaletteIds(values, palette.length);
181
+ // --- Write palette size ---
182
+ const paletteSizeBuf = writeInt32LE(palette.length);
183
+ // --- Write palette values ---
184
+ const paletteBufs = palette.map((v) => writeInt32LE(v));
185
+ buffers.push(idsBuf, paletteSizeBuf, ...paletteBufs);
186
+ }
187
+ return Buffer.concat(buffers);
188
+ }
189
+ function readSubchunkPaletteIds(buffer, offset, end) {
190
+ let p = offset;
191
+ if (end - p < 1) {
192
+ throw new Error("Subchunk biome error: not enough data for flags.");
193
+ }
194
+ const flags = buffer[p++];
195
+ const isPersistent = (flags & 1) === 0;
196
+ const bitsPerBlock = flags >> 1;
197
+ // Special case: palette copy
198
+ if (bitsPerBlock === 127) {
199
+ return { values: null, isPersistent, paletteSize: 0, newOffset: p };
200
+ }
201
+ const values = new Array(4096);
202
+ if (bitsPerBlock > 0) {
203
+ const blocksPerWord = Math.floor(32 / bitsPerBlock);
204
+ const wordCount = Math.floor(4095 / blocksPerWord) + 1;
205
+ const mask = (1 << bitsPerBlock) - 1;
206
+ if (end - p < 4 * wordCount) {
207
+ throw new Error("Subchunk biome error: not enough data for block words.");
208
+ }
209
+ let u = 0;
210
+ for (let j = 0; j < wordCount; j++) {
211
+ let temp = buffer[p] | (buffer[p + 1] << 8) | (buffer[p + 2] << 16) | (buffer[p + 3] << 24);
212
+ p += 4;
213
+ for (let k = 0; k < blocksPerWord && u < 4096; k++) {
214
+ // const x = (u >> 8) & 0xf;
215
+ // const z = (u >> 4) & 0xf;
216
+ // const y = u & 0xf;
217
+ values[u] = (temp & mask) + 1; // +1 because R stored 1-based
218
+ temp >>= bitsPerBlock;
219
+ u++;
220
+ }
221
+ }
222
+ if (end - p < 4) {
223
+ throw new Error("Subchunk biome error: missing palette size.");
224
+ }
225
+ const paletteSize = buffer[p] | (buffer[p + 1] << 8) | (buffer[p + 2] << 16) | (buffer[p + 3] << 24);
226
+ p += 4;
227
+ return { values, isPersistent, paletteSize, newOffset: p };
228
+ }
229
+ else {
230
+ // bitsPerBlock == 0 -> everything is ID 1
231
+ for (let u = 0; u < 4096; u++) {
232
+ values[u] = 1;
233
+ }
234
+ return { values, isPersistent, paletteSize: 1, newOffset: p };
235
+ }
236
+ }
237
+ function writeSubchunkPaletteIds(values, paletteSize) {
238
+ const blockCount = values.length; // usually 16*16*16 = 4096
239
+ const bitsPerBlock = Math.max(1, Math.ceil(Math.log2(paletteSize)));
240
+ const wordsPerBlock = Math.ceil((blockCount * bitsPerBlock) / 32);
241
+ const words = new Uint32Array(wordsPerBlock);
242
+ // let bitIndex: number = 0;
243
+ // for (const v of values) {
244
+ // let idx: number = v >>> 0; // ensure unsigned
245
+ // for (let i: number = 0; i < bitsPerBlock; i++) {
246
+ // if (idx & (1 << i)) {
247
+ // const wordIndex: number = Math.floor(bitIndex / 32);
248
+ // const bitOffset: number = bitIndex % 32;
249
+ // words[wordIndex]! |= 1 << bitOffset;
250
+ // }
251
+ // bitIndex++;
252
+ // }
253
+ // }
254
+ let bitIndex = 0;
255
+ for (let i = 0; i < values.length; i++) {
256
+ let val = values[i] & ((1 << bitsPerBlock) - 1); // mask to bpe bits
257
+ const wordIndex = Math.floor(bitIndex / 32);
258
+ const bitOffset = bitIndex % 32;
259
+ words[wordIndex] |= val << bitOffset;
260
+ if (bitOffset + bitsPerBlock > 32) {
261
+ // spill over into next word
262
+ words[wordIndex + 1] |= val >>> (32 - bitOffset);
263
+ }
264
+ bitIndex += bitsPerBlock;
265
+ }
266
+ words.forEach((val, i) => {
267
+ words[i] = (-val - 1) & 0xffffffff;
268
+ });
269
+ // --- Write header byte ---
270
+ const header = Buffer.alloc(1);
271
+ header.writeUInt8((bitsPerBlock << 1) | 1, 0);
272
+ // --- Write packed data ---
273
+ const packed = Buffer.alloc(words.length * 4);
274
+ for (let i = 0; i < words.length; i++) {
275
+ packed.writeUInt32LE(words[i], i * 4);
276
+ }
277
+ return Buffer.concat([header, packed]);
278
+ }
279
+ //#endregion Local Functions
280
+ // --------------------------------------------------------------------------------
281
+ // Constants
282
+ // --------------------------------------------------------------------------------
283
+ //#region Constants
284
+ /**
285
+ * The list of Minecraft dimensions.
286
+ */
287
+ export const dimensions = ["overworld", "nether", "the_end"];
288
+ /**
289
+ * The list of Minecraft game modes, with their indices corresponding to their numeric gamemode IDs.
290
+ */
291
+ export const gameModes = ["survival", "creative", "adventure", , , "default", "spectator"];
292
+ /**
293
+ * The content types for LevelDB entries.
294
+ */
295
+ export const DBEntryContentTypes = [
296
+ // Biome Linked
297
+ "Data3D",
298
+ "Version",
299
+ "Data2D",
300
+ "Data2DLegacy",
301
+ "SubChunkPrefix",
302
+ "LegacyTerrain",
303
+ "BlockEntity",
304
+ "Entity",
305
+ "PendingTicks",
306
+ "LegacyBlockExtraData",
307
+ "BiomeState",
308
+ "FinalizedState",
309
+ "ConversionData",
310
+ "BorderBlocks",
311
+ "HardcodedSpawners",
312
+ "RandomTicks",
313
+ "Checksums",
314
+ "MetaDataHash",
315
+ "GeneratedPreCavesAndCliffsBlending",
316
+ "BlendingBiomeHeight",
317
+ "BlendingData",
318
+ "ActorDigestVersion",
319
+ "LegacyVersion",
320
+ "AABBVolumes", // TO-DO: Figure out how to parse this, it seems to have data for trial chambers and trail ruins.
321
+ // Village
322
+ "VillageDwellers",
323
+ "VillageInfo",
324
+ "VillagePOI",
325
+ "VillagePlayers",
326
+ // Standalone
327
+ "Player",
328
+ "PlayerClient",
329
+ "ActorPrefix",
330
+ "Digest",
331
+ "Map",
332
+ "Portals",
333
+ "SchedulerWT",
334
+ "StructureTemplate",
335
+ "TickingArea",
336
+ "FlatWorldLayers",
337
+ "Scoreboard",
338
+ "Dimension",
339
+ "AutonomousEntities",
340
+ "BiomeData",
341
+ "MobEvents",
342
+ "DynamicProperties",
343
+ "LevelChunkMetaDataDictionary",
344
+ "RealmsStoriesData",
345
+ "LevelDat",
346
+ // Dev Version
347
+ "ForcedWorldCorruption",
348
+ // Misc.
349
+ "Unknown",
350
+ ];
351
+ /**
352
+ * The content type to format mapping for LevelDB entries.
353
+ */
354
+ export const entryContentTypeToFormatMap = {
355
+ /**
356
+ * @todo Make a parser for this.
357
+ */
358
+ Data3D: {
359
+ type: "custom",
360
+ resultType: "JSONNBT",
361
+ parse(data) {
362
+ const result = readData3dValue(data);
363
+ return {
364
+ type: "compound",
365
+ value: {
366
+ heightMap: {
367
+ type: "list",
368
+ value: {
369
+ type: "list",
370
+ value: result.heightMap.map((row) => ({
371
+ type: "short",
372
+ value: row,
373
+ })),
374
+ },
375
+ },
376
+ biomes: {
377
+ type: "list",
378
+ value: {
379
+ type: "compound",
380
+ value: result.biomes.map((subchunk) => ({
381
+ values: {
382
+ type: "list",
383
+ value: subchunk.values
384
+ ? {
385
+ type: "int",
386
+ value: subchunk.values,
387
+ }
388
+ : {
389
+ type: "end",
390
+ value: [],
391
+ },
392
+ },
393
+ palette: {
394
+ type: "list",
395
+ value: {
396
+ type: "int",
397
+ value: subchunk.palette,
398
+ },
399
+ },
400
+ })),
401
+ },
402
+ },
403
+ },
404
+ };
405
+ },
406
+ serialize(data) {
407
+ return writeData3DValue(data.value.heightMap.value.value.map((row) => row.value), data.value.biomes.value.value.map((subchunk) => ({
408
+ palette: subchunk.palette.value.value,
409
+ values: !subchunk.values.value.value?.length ? null : subchunk.values.value.value,
410
+ })));
411
+ },
412
+ },
413
+ Version: { type: "int", bytes: 1, format: "LE", signed: false },
414
+ /**
415
+ * @deprecated Only used in versions < 1.18.0.
416
+ *
417
+ * @todo Make a parser for this so that versions < 1.18.0 can be supported.
418
+ */
419
+ Data2D: { type: "unknown" },
420
+ /**
421
+ * @deprecated Only used in versions < 1.0.0.
422
+ */
423
+ Data2DLegacy: { type: "unknown" },
424
+ SubChunkPrefix: {
425
+ type: "custom",
426
+ resultType: "JSONNBT",
427
+ async parse(data) {
428
+ let currentOffset = 0;
429
+ const layers = [];
430
+ /**
431
+ * The version of the SubChunkPrefix.
432
+ *
433
+ * Should be `0x08` (1.2.13 <= x < 1.18.0) or `0x09` (1.18.0 <= x).
434
+ */
435
+ const version = data[currentOffset++];
436
+ if (![0x08, 0x09].includes(version))
437
+ throw new Error(`Unknown SubChunkPrefix version: ${version}`);
438
+ /**
439
+ * How many blocks are in each location (ex. 2 might mean here is a waterlog layer).
440
+ */
441
+ let numStorageBlocks = data[currentOffset++];
442
+ const subChunkIndex = version >= 0x09 ? data[currentOffset++] : undefined;
443
+ for (let blockIndex = 0; blockIndex < numStorageBlocks; blockIndex++) {
444
+ const storageVersion = data[currentOffset];
445
+ currentOffset++;
446
+ const bitsPerBlock = storageVersion >> 1;
447
+ const blocksPerWord = Math.floor(32 / bitsPerBlock);
448
+ const numints = Math.ceil(4096 / blocksPerWord);
449
+ const blockDataOffset = currentOffset;
450
+ let paletteOffset = blockDataOffset + 4 * numints;
451
+ let psize = bitsPerBlock === 0 ? 0 : getInt32Val(data, paletteOffset);
452
+ paletteOffset += Math.sign(bitsPerBlock) * 4;
453
+ // const debugInfo = {
454
+ // version,
455
+ // blockIndex,
456
+ // storageVersion,
457
+ // bitsPerBlock,
458
+ // blocksPerWord,
459
+ // numints,
460
+ // blockDataOffset,
461
+ // paletteOffset,
462
+ // psize,
463
+ // // blockData,
464
+ // // palette,
465
+ // };
466
+ const palette = {};
467
+ for (let i = 0n; i < psize; i++) {
468
+ // console.log(debugInfo);
469
+ // appendFileSync("./test1.bin", JSON.stringify(debugInfo) + "\n");
470
+ const result = (await NBT.parse(data.subarray(paletteOffset), "little"));
471
+ paletteOffset += result.metadata.size;
472
+ palette[`${i}`] = result.parsed;
473
+ // console.log(result);
474
+ // appendFileSync("./test1.bin", JSON.stringify(result));
475
+ }
476
+ currentOffset = paletteOffset;
477
+ const block_indices = [];
478
+ for (let i = 0; i < 4096; i++) {
479
+ const maskVal = getInt32Val(data, blockDataOffset + Math.floor(i / blocksPerWord) * 4);
480
+ const blockVal = (maskVal >> ((i % blocksPerWord) * bitsPerBlock)) & ((1 << bitsPerBlock) - 1);
481
+ // const blockType: DataTypes_Block | undefined = palette[`${blockVal as unknown as bigint}`];
482
+ // if (!blockType && blockVal !== -1) throw new ReferenceError(`Invalid block palette index ${blockVal} for block ${i}`);
483
+ /* const chunkOffset: Vector3 = {
484
+ x: (i >> 8) & 0xf,
485
+ y: (i >> 4) & 0xf,
486
+ z: (i >> 0) & 0xf,
487
+ }; */
488
+ block_indices.push(blockVal);
489
+ }
490
+ layers.push({
491
+ storageVersion: NBT.byte(storageVersion),
492
+ palette: NBT.comp(palette),
493
+ block_indices: { type: "list", value: { type: "int", value: block_indices } },
494
+ });
495
+ }
496
+ return NBT.comp({
497
+ version: NBT.byte(version),
498
+ layerCount: NBT.byte(numStorageBlocks),
499
+ layers: { type: "list", value: NBT.comp(layers) },
500
+ ...(version >= 0x09
501
+ ? {
502
+ subChunkIndex: NBT.byte(subChunkIndex),
503
+ }
504
+ : {}),
505
+ });
506
+ },
507
+ serialize(data) {
508
+ const buffer = Buffer.from([
509
+ data.value.version.value,
510
+ data.value.layerCount.value,
511
+ ...(data.value.version.value >= 0x09 ? [data.value.subChunkIndex.value] : []),
512
+ ]);
513
+ const layerBuffers = data.value.layers.value.value.map((layer) => {
514
+ const bitsPerBlock = layer.storageVersion.value >> 1;
515
+ const blocksPerWord = Math.floor(32 / bitsPerBlock);
516
+ const numints = Math.ceil(4096 / blocksPerWord);
517
+ const bytes = [layer.storageVersion.value];
518
+ const blockIndicesBuffer = Buffer.alloc(Math.ceil(numints * 4));
519
+ writeBlockIndices(blockIndicesBuffer, 0, layer.block_indices.value.value, bitsPerBlock, blocksPerWord);
520
+ bytes.push(...blockIndicesBuffer);
521
+ const paletteLengthBuffer = Buffer.alloc(4);
522
+ setInt32Val(paletteLengthBuffer, 0, Object.keys(layer.palette.value).length);
523
+ bytes.push(...paletteLengthBuffer);
524
+ const paletteKeys = Object.keys(layer.palette.value).sort((a, b) => Number(a) - Number(b));
525
+ for (let paletteIndex = 0; paletteIndex < paletteKeys.length; paletteIndex++) {
526
+ const block = layer.palette.value[paletteKeys[paletteIndex]];
527
+ bytes.push(...NBT.writeUncompressed({ name: "", ...block }, "little"));
528
+ }
529
+ return Buffer.from(bytes);
530
+ });
531
+ return Buffer.concat([buffer, ...layerBuffers]);
532
+ },
533
+ },
534
+ /**
535
+ * @deprecated Only used in versions < 1.0.0.
536
+ */
537
+ LegacyTerrain: { type: "unknown" },
538
+ BlockEntity: { type: "NBT" },
539
+ Entity: { type: "NBT" },
540
+ PendingTicks: { type: "NBT" },
541
+ /**
542
+ * @deprecated Only used in versions < 1.2.3.
543
+ */
544
+ LegacyBlockExtraData: { type: "unknown" },
545
+ /**
546
+ * @todo Figure out how to parse this.
547
+ */
548
+ BiomeState: { type: "unknown" },
549
+ FinalizedState: { type: "int", bytes: 4, format: "LE", signed: false },
550
+ /**
551
+ * @deprecated No longer used.
552
+ */
553
+ ConversionData: { type: "unknown" },
554
+ /**
555
+ * @todo Figure out how to parse this.
556
+ */
557
+ BorderBlocks: { type: "unknown" },
558
+ HardcodedSpawners: { type: "NBT" },
559
+ RandomTicks: { type: "NBT" },
560
+ /**
561
+ * @deprecated Only used in versions < 1.18.0.
562
+ */
563
+ Checksums: { type: "unknown" },
564
+ /**
565
+ * @todo Figure out how to parse this.
566
+ */
567
+ MetaDataHash: { type: "unknown" },
568
+ /**
569
+ * @deprecated Unused.
570
+ */
571
+ GeneratedPreCavesAndCliffsBlending: { type: "unknown" },
572
+ /**
573
+ * @todo Figure out how to parse this.
574
+ */
575
+ BlendingBiomeHeight: { type: "unknown" },
576
+ /**
577
+ * @todo Figure out how to parse this.
578
+ */
579
+ BlendingData: { type: "unknown" },
580
+ /**
581
+ * @todo Figure out how to parse this.
582
+ */
583
+ ActorDigestVersion: { type: "unknown" },
584
+ /**
585
+ * @deprecated Only used in versions < 1.16.100. Later versions use {@link entryContentTypeToFormatMap.Version}
586
+ */
587
+ LegacyVersion: { type: "int", bytes: 1, format: "LE", signed: false },
588
+ VillageDwellers: { type: "NBT" },
589
+ VillageInfo: { type: "NBT" },
590
+ VillagePOI: { type: "NBT" },
591
+ VillagePlayers: { type: "NBT" },
592
+ Player: { type: "NBT" },
593
+ PlayerClient: { type: "NBT" },
594
+ ActorPrefix: { type: "NBT", format: "LE" },
595
+ /**
596
+ * @todo Figure out how to parse this.
597
+ */
598
+ Digest: { type: "unknown" },
599
+ Map: { type: "NBT" },
600
+ Portals: { type: "NBT" },
601
+ SchedulerWT: { type: "NBT" },
602
+ StructureTemplate: { type: "NBT" },
603
+ TickingArea: { type: "NBT" },
604
+ FlatWorldLayers: { type: "ASCII" },
605
+ Scoreboard: { type: "NBT" },
606
+ Dimension: { type: "NBT" },
607
+ AutonomousEntities: { type: "NBT" },
608
+ BiomeData: { type: "NBT" },
609
+ MobEvents: { type: "NBT" },
610
+ LevelDat: { type: "NBT" },
611
+ /**
612
+ * @todo Figure out how to parse this.
613
+ */
614
+ AABBVolumes: { type: "unknown" },
615
+ DynamicProperties: { type: "NBT" },
616
+ /**
617
+ * @todo Figure out how to parse this. (It looks like NBT with a bit of extra data at the beginning.)
618
+ */
619
+ LevelChunkMetaDataDictionary: { type: "unknown" },
620
+ /**
621
+ * @todo Figure out how to parse this. (It seems that each one just has a value of 1 (`0x31`).)
622
+ */
623
+ RealmsStoriesData: { type: "unknown" },
624
+ /**
625
+ * The content type used for LevelDB keys that are used for the forced world corruption feature of the developer version of Bedrock Edition.
626
+ */
627
+ ForcedWorldCorruption: { type: "unknown" },
628
+ /**
629
+ * All data that has a key that is not recognized.
630
+ */
631
+ Unknown: { type: "unknown" },
632
+ };
633
+ //#endregion Types
634
+ // --------------------------------------------------------------------------------
635
+ // Functions
636
+ // --------------------------------------------------------------------------------
637
+ //#region Functions
638
+ /**
639
+ * Parses an integer from a buffer gives the number of bytes, endianness, signedness and offset.
640
+ *
641
+ * @param buffer The buffer to read from.
642
+ * @param bytes The number of bytes to read.
643
+ * @param format The endianness of the data.
644
+ * @param signed The signedness of the data. Defaults to `false`.
645
+ * @param offset The offset to read from. Defaults to `0`.
646
+ * @returns The parsed integer.
647
+ *
648
+ * @throws {RangeError} If the byte length is less than 1.
649
+ * @throws {RangeError} If the buffer does not contain enough data at the specified offset.
650
+ */
651
+ export function parseSpecificIntType(buffer, bytes, format, signed = false, offset = 0) {
652
+ if (bytes < 1) {
653
+ throw new RangeError("Byte length must be at least 1");
654
+ }
655
+ if (offset + bytes > buffer.length) {
656
+ throw new RangeError("Buffer does not contain enough data at the specified offset");
657
+ }
658
+ let result = 0n;
659
+ if (format === "BE") {
660
+ for (let i = 0; i < bytes; i++) {
661
+ result = (result << 8n) | BigInt(buffer[offset + i]);
662
+ }
663
+ }
664
+ else {
665
+ for (let i = bytes - 1; i >= 0; i--) {
666
+ result = (result << 8n) | BigInt(buffer[offset + i]);
667
+ }
668
+ }
669
+ if (signed) {
670
+ const signBit = 1n << BigInt(bytes * 8 - 1);
671
+ if (result & signBit) {
672
+ result -= 1n << BigInt(bytes * 8);
673
+ }
674
+ }
675
+ return result;
676
+ }
677
+ /**
678
+ * Writes an integer to a buffer.
679
+ *
680
+ * @param buffer The buffer to write to.
681
+ * @param value The integer to write.
682
+ * @param bytes The number of bytes to write.
683
+ * @param format The endianness of the data.
684
+ * @param signed The signedness of the data. Defaults to `false`.
685
+ * @param offset The offset to write to. Defaults to `0`.
686
+ * @param options The options to use.
687
+ * @returns The buffer from the {@link buffer} parameter.
688
+ *
689
+ * @throws {RangeError} If the byte length is less than 1.
690
+ * @throws {RangeError} If the buffer does not have enough space at the specified offset.
691
+ * @throws {RangeError} If the value is out of range and {@link WriteSpecificIntTypeOptions.wrap | options.wrap} is `false`.
692
+ */
693
+ export function writeSpecificIntType(buffer, value, bytes, format, signed = false, offset = 0, options) {
694
+ if (bytes < 1) {
695
+ throw new RangeError("Byte length must be at least 1");
696
+ }
697
+ if (offset + bytes > buffer.length) {
698
+ throw new RangeError("Buffer does not have enough space at the specified offset");
699
+ }
700
+ const bitSize = BigInt(bytes * 8);
701
+ const maxUnsigned = (1n << bitSize) - 1n;
702
+ const minSigned = -(1n << (bitSize - 1n));
703
+ const maxSigned = (1n << (bitSize - 1n)) - 1n;
704
+ if (signed) {
705
+ if (value < minSigned || value > maxSigned) {
706
+ if (options?.wrap) {
707
+ value = (value + (1n << bitSize)) % (1n << bitSize);
708
+ }
709
+ else {
710
+ throw new RangeError(`Signed value out of range for ${bytes} bytes`);
711
+ }
712
+ }
713
+ if (value < 0n) {
714
+ value += 1n << bitSize;
715
+ }
716
+ }
717
+ else {
718
+ if (value < 0n || value > maxUnsigned) {
719
+ if (options?.wrap) {
720
+ value = value % (1n << bitSize);
721
+ }
722
+ else {
723
+ throw new RangeError(`Unsigned value out of range for ${bytes} bytes`);
724
+ }
725
+ }
726
+ }
727
+ for (let i = 0; i < bytes; i++) {
728
+ const shift = format === "BE" ? BigInt((bytes - 1 - i) * 8) : BigInt(i * 8);
729
+ buffer[offset + i] = Number((value >> shift) & 0xffn);
730
+ }
731
+ return buffer;
732
+ }
733
+ /**
734
+ * Sanitizes a filename.
735
+ *
736
+ * @param filename The filename to sanitize.
737
+ * @returns The sanitized filename.
738
+ */
739
+ export function sanitizeFilename(filename) {
740
+ return filename
741
+ .replaceAll(fileNameCharacterFilterRegExp, /* (substring: string): string => encodeURIComponent(substring) */ "")
742
+ .replaceAll(fileNameEncodeCharacterRegExp, (substring) => encodeURIComponent(substring));
743
+ }
744
+ /**
745
+ * Sanitizes a display key.
746
+ *
747
+ * @param key The key to sanitize.
748
+ * @returns The sanitized key.
749
+ */
750
+ export function sanitizeDisplayKey(key) {
751
+ return key.replaceAll(/[^a-zA-Z0-9-_+,-.;=@~/:>?\\]/g, (substring) => encodeURIComponent(substring) /* "" */);
752
+ }
753
+ /**
754
+ * Converts a chunk block index to an offset from the minimum corner of the chunk.
755
+ *
756
+ * @param index The chunk block index.
757
+ * @returns The offset from the minimum corner of the chunk.
758
+ */
759
+ export function chunkBlockIndexToOffset(index) {
760
+ return {
761
+ x: (index >> 8) & 0xf,
762
+ y: (index >> 4) & 0xf,
763
+ z: (index >> 0) & 0xf,
764
+ };
765
+ }
766
+ /**
767
+ * Converts an offset from the minimum corner of the chunk to a chunk block index.
768
+ *
769
+ * @param offset The offset from the minimum corner of the chunk.
770
+ * @returns The chunk block index.
771
+ */
772
+ export function offsetToChunkBlockIndex(offset) {
773
+ return (offset.x & 0xf) | ((offset.y & 0xf) << 4) | ((offset.z & 0xf) << 8);
774
+ }
775
+ /**
776
+ * Reads a 32-bit integer value from a Buffer at the given offset (little-endian).
777
+ *
778
+ * @param data The Buffer to read from.
779
+ * @param offset The offset to read from.
780
+ * @returns The 32-bit integer value.
781
+ */
782
+ export function getInt32Val(data, offset) {
783
+ let retval = 0;
784
+ // need to switch this to union based like the others.
785
+ for (let i = 0; i < 4; i++) {
786
+ // if I don't do the static cast, the top bit will be sign extended.
787
+ retval |= data[offset + i] << (i * 8);
788
+ }
789
+ return retval;
790
+ }
791
+ /**
792
+ * Writes a 32-bit integer value into a Buffer at the given offset (little-endian).
793
+ *
794
+ * @param buffer The Buffer to write into.
795
+ * @param offset The offset to write into.
796
+ * @param value The 32-bit integer value to write.
797
+ */
798
+ export function setInt32Val(buffer, offset, value) {
799
+ for (let i = 0; i < 4; i++) {
800
+ buffer[offset + i] = (value >> (i * 8)) & 0xff;
801
+ }
802
+ }
803
+ /**
804
+ * Splits a range into smaller ranges of a given size.
805
+ *
806
+ * @param param0 The range to split.
807
+ * @param size The size of each range.
808
+ * @returns The split ranges.
809
+ */
810
+ export function splitRange([min, max], size) {
811
+ const result = [];
812
+ let start = min;
813
+ while (start <= max) {
814
+ const end = Math.min(start + size - 1, max);
815
+ result.push([start, end]);
816
+ start = end + 1;
817
+ }
818
+ return result;
819
+ }
820
+ /**
821
+ * Packs block indices into the buffer using the same scheme as the read loop.
822
+ *
823
+ * @param buffer The buffer to write into.
824
+ * @param blockDataOffset The offset where block data begins in the buffer.
825
+ * @param block_indices The list of block indices to pack.
826
+ * @param bitsPerBlock The number of bits used per block.
827
+ * @param blocksPerWord How many blocks fit inside one 32-bit integer.
828
+ */
829
+ export function writeBlockIndices(buffer, blockDataOffset, block_indices, bitsPerBlock, blocksPerWord) {
830
+ const wordCount = Math.ceil(block_indices.length / blocksPerWord);
831
+ for (let wordIndex = 0; wordIndex < wordCount; wordIndex++) {
832
+ let maskVal = 0;
833
+ for (let j = 0; j < blocksPerWord; j++) {
834
+ const blockIndex = wordIndex * blocksPerWord + j;
835
+ if (blockIndex >= block_indices.length)
836
+ break;
837
+ const blockVal = block_indices[blockIndex];
838
+ const shiftAmount = j * bitsPerBlock;
839
+ maskVal |= blockVal /* & ((1 << bitsPerBlock) - 1) */ << shiftAmount;
840
+ }
841
+ setInt32Val(buffer, blockDataOffset + wordIndex * 4, maskVal);
842
+ }
843
+ }
844
+ /**
845
+ * Gets the chunk indices from a LevelDB key.
846
+ *
847
+ * The key must be a [chunk key](https://minecraft.wiki/w/Bedrock_Edition_level_format#Chunk_key_format).
848
+ *
849
+ * @param key The key to get the chunk indices for, as a Buffer.
850
+ * @returns The chunk indices.
851
+ */
852
+ export function getChunkKeyIndices(key) {
853
+ return {
854
+ x: getInt32Val(key, 0),
855
+ z: getInt32Val(key, 4),
856
+ dimension: [13, 14].includes(key.length) ? dimensions[getInt32Val(key, 8)] ?? "overworld" : "overworld",
857
+ subChunkIndex: [10, 14].includes(key.length) ? key.at(-1) : undefined,
858
+ };
859
+ }
860
+ /**
861
+ * Gets a human-readable version of a LevelDB key.
862
+ *
863
+ * @param key The key to get the display name for, as a Buffer.
864
+ * @returns A human-readable version of the key.
865
+ */
866
+ export function getKeyDisplayName(key) {
867
+ const contentType = getContentTypeFromDBKey(key);
868
+ switch (contentType) {
869
+ case "Data3D":
870
+ case "Version":
871
+ case "Data2D":
872
+ case "Data2DLegacy":
873
+ case "SubChunkPrefix":
874
+ case "LegacyTerrain":
875
+ case "BlockEntity":
876
+ case "Entity":
877
+ case "PendingTicks":
878
+ case "LegacyBlockExtraData":
879
+ case "BiomeState":
880
+ case "FinalizedState":
881
+ case "ConversionData":
882
+ case "BorderBlocks":
883
+ case "HardcodedSpawners":
884
+ case "RandomTicks":
885
+ case "Checksums":
886
+ case "MetaDataHash":
887
+ case "GeneratedPreCavesAndCliffsBlending":
888
+ case "BlendingBiomeHeight":
889
+ case "BlendingData":
890
+ case "ActorDigestVersion":
891
+ case "LegacyVersion":
892
+ case "AABBVolumes": {
893
+ const indices = getChunkKeyIndices(key);
894
+ return `${indices.dimension}_${indices.x}_${indices.z}${"subChunkIndex" in indices && indices.subChunkIndex ? `_${indices.subChunkIndex}` : ""}_${contentType}`;
895
+ }
896
+ case "Digest": {
897
+ const indices = getChunkKeyIndices(key.subarray(4));
898
+ return `digp_${indices.dimension}_${indices.x}_${indices.z}`;
899
+ }
900
+ case "ActorPrefix": {
901
+ return `actorprefix_${getInt32Val(key, key.length - 8)}_${getInt32Val(key, key.length - 4)}`;
902
+ }
903
+ case "AutonomousEntities":
904
+ case "BiomeData":
905
+ case "Dimension":
906
+ case "DynamicProperties":
907
+ case "FlatWorldLayers":
908
+ case "ForcedWorldCorruption":
909
+ case "LevelChunkMetaDataDictionary":
910
+ case "LevelDat":
911
+ case "Map":
912
+ case "MobEvents":
913
+ case "Player":
914
+ case "PlayerClient":
915
+ case "Portals":
916
+ case "RealmsStoriesData":
917
+ case "SchedulerWT":
918
+ case "Scoreboard":
919
+ case "StructureTemplate":
920
+ case "TickingArea":
921
+ case "VillageDwellers":
922
+ case "VillageInfo":
923
+ case "VillagePOI":
924
+ case "VillagePlayers":
925
+ case "Unknown":
926
+ default:
927
+ return key.toString("binary");
928
+ }
929
+ }
930
+ /**
931
+ * Gets the content type of a LevelDB key.
932
+ *
933
+ * @param key The key to get the content type for, as a Buffer.
934
+ * @returns The content type of the key.
935
+ */
936
+ export function getContentTypeFromDBKey(key) {
937
+ if ([9, 10, 13, 14].includes(key.length)) {
938
+ switch (key.at([10, 14].includes(key.length) ? -2 : -1)) {
939
+ case 0x2b:
940
+ return "Data3D";
941
+ case 0x2c:
942
+ return "Version";
943
+ case 0x2d:
944
+ return "Data2D";
945
+ case 0x2e:
946
+ return "Data2DLegacy";
947
+ case 0x2f:
948
+ return "SubChunkPrefix";
949
+ case 0x30:
950
+ return "LegacyTerrain";
951
+ case 0x31:
952
+ return "BlockEntity";
953
+ case 0x32:
954
+ return "Entity";
955
+ case 0x33:
956
+ return "PendingTicks";
957
+ case 0x34:
958
+ return "LegacyBlockExtraData";
959
+ case 0x35:
960
+ return "BiomeState";
961
+ case 0x36:
962
+ return "FinalizedState";
963
+ case 0x37:
964
+ return "ConversionData";
965
+ case 0x38:
966
+ return "BorderBlocks";
967
+ case 0x39:
968
+ return "HardcodedSpawners";
969
+ case 0x3a:
970
+ return "RandomTicks";
971
+ case 0x3b:
972
+ return "Checksums";
973
+ case 0x3d:
974
+ return "MetaDataHash";
975
+ case 0x3e:
976
+ return "GeneratedPreCavesAndCliffsBlending";
977
+ case 0x3f:
978
+ return "BlendingBiomeHeight";
979
+ case 0x40:
980
+ return "BlendingData";
981
+ case 0x41:
982
+ return "ActorDigestVersion";
983
+ case 0x76:
984
+ return "LegacyVersion";
985
+ case 0x77:
986
+ return "AABBVolumes";
987
+ }
988
+ }
989
+ const stringKey = key.toString();
990
+ switch (stringKey) {
991
+ case "~local_player":
992
+ return "Player";
993
+ case "game_flatworldlayers":
994
+ return "FlatWorldLayers";
995
+ case "Overworld":
996
+ case "Nether":
997
+ case "TheEnd":
998
+ return "Dimension";
999
+ case "mobevents":
1000
+ return "MobEvents";
1001
+ case "BiomeData":
1002
+ return "BiomeData";
1003
+ case "AutonomousEntities":
1004
+ return "AutonomousEntities";
1005
+ case "scoreboard":
1006
+ return "Scoreboard";
1007
+ case "schedulerWT":
1008
+ return "SchedulerWT";
1009
+ case "portals":
1010
+ return "Portals";
1011
+ case "DynamicProperties":
1012
+ return "DynamicProperties";
1013
+ case "LevelChunkMetaDataDictionary":
1014
+ return "LevelChunkMetaDataDictionary";
1015
+ case "SST_SALOG":
1016
+ case "SST_WORD":
1017
+ case "SST_WORD_":
1018
+ case "DedicatedServerForcedCorruption":
1019
+ return "ForcedWorldCorruption";
1020
+ }
1021
+ switch (true) {
1022
+ case stringKey.startsWith("actorprefix"):
1023
+ return "ActorPrefix";
1024
+ case stringKey.startsWith("structuretemplate"):
1025
+ return "StructureTemplate";
1026
+ case stringKey.startsWith("tickingarea"):
1027
+ return "TickingArea";
1028
+ case stringKey.startsWith("portals"):
1029
+ return "Portals";
1030
+ case stringKey.startsWith("map_"):
1031
+ return "Map";
1032
+ case stringKey.startsWith("player_server_"):
1033
+ return "Player";
1034
+ case stringKey.startsWith("player_"):
1035
+ return "PlayerClient";
1036
+ case stringKey.startsWith("digp"):
1037
+ return "Digest";
1038
+ case stringKey.startsWith("RealmsStoriesData_"):
1039
+ return "RealmsStoriesData";
1040
+ case /^VILLAGE_(?:[Oo][Vv][Ee][Rr][Ww][Oo][Rr][Ll][Dd]|[Tt][Hh][Ee]_[Ee][Nn][Dd]|[Nn][Ee][Tt][Hh][Ee][Rr])_[0-9a-f\\-]+_POI$/.test(stringKey):
1041
+ return "VillagePOI";
1042
+ case /^VILLAGE_(?:[Oo][Vv][Ee][Rr][Ww][Oo][Rr][Ll][Dd]|[Tt][Hh][Ee]_[Ee][Nn][Dd]|[Nn][Ee][Tt][Hh][Ee][Rr])_[0-9a-f\\-]+_INFO$/.test(stringKey):
1043
+ return "VillageInfo";
1044
+ case /^VILLAGE_(?:[Oo][Vv][Ee][Rr][Ww][Oo][Rr][Ll][Dd]|[Tt][Hh][Ee]_[Ee][Nn][Dd]|[Nn][Ee][Tt][Hh][Ee][Rr])_[0-9a-f\\-]+_DWELLERS$/.test(stringKey):
1045
+ return "VillageDwellers";
1046
+ case /^VILLAGE_(?:[Oo][Vv][Ee][Rr][Ww][Oo][Rr][Ll][Dd]|[Tt][Hh][Ee]_[Ee][Nn][Dd]|[Nn][Ee][Tt][Hh][Ee][Rr])_[0-9a-f\\-]+_PLAYERS$/.test(stringKey):
1047
+ return "VillagePlayers";
1048
+ }
1049
+ return "Unknown";
1050
+ }
1051
+ /**
1052
+ * Gets the biome type from its ID.
1053
+ *
1054
+ * @param id The ID of the biome.
1055
+ * @returns The biome type.
1056
+ */
1057
+ export function getBiomeTypeFromID(id) {
1058
+ return Object.keys(BiomeData.int_map).find((key) => BiomeData.int_map[key] === id);
1059
+ }
1060
+ /**
1061
+ * Gets the biome ID from its type.
1062
+ *
1063
+ * @param type The type of the biome.
1064
+ * @returns The biome ID.
1065
+ */
1066
+ export function getBiomeIDFromType(type) {
1067
+ return BiomeData.int_map[type];
1068
+ }
1069
+ // Removed as it turns out the long tags were just having their high and low parts swapped.
1070
+ // /**
1071
+ // * Converts a 64-bit Q32.32 Unix timestamp to a Date object.
1072
+ // *
1073
+ // * A 64-bit Q32.32 Unix timestamp is the format of the LastPlayed property in `level.dat`.
1074
+ // *
1075
+ // * @param q The timestamp to convert.
1076
+ // * @returns The corresponding Date object.
1077
+ // */
1078
+ // export function q32_32ToDate(q: bigint): Date {
1079
+ // // extract whole seconds
1080
+ // const seconds: number = Number(q >> 32n);
1081
+ // // extract fractional part (0 .. 2^32-1)
1082
+ // const frac: number = Number(q & 0xffff_ffffn);
1083
+ // // convert fraction to milliseconds
1084
+ // const millisFrac: number = (frac * 1000) / 2 ** 32;
1085
+ // // total milliseconds since epoch
1086
+ // const ms: number = seconds * 1000 + millisFrac;
1087
+ // return new Date(ms);
1088
+ // }
1089
+ // /**
1090
+ // * Converts a Date object to a 64-bit Q32.32 Unix timestamp.
1091
+ // *
1092
+ // * A 64-bit Q32.32 Unix timestamp is the format of the LastPlayed property in `level.dat`.
1093
+ // *
1094
+ // * @param q The Date object to convert.
1095
+ // * @returns The corresponding timestamp.
1096
+ // */
1097
+ // export function dateToQ32_32(date: Date): bigint {
1098
+ // // milliseconds since epoch
1099
+ // const ms: number = date.getTime();
1100
+ // // whole seconds
1101
+ // const seconds: bigint = BigInt(Math.floor(ms / 1000));
1102
+ // // fractional part in milliseconds
1103
+ // const msFrac: number = ms % 1000;
1104
+ // // convert fraction to Q32.32 units (scale milliseconds to 2^32)
1105
+ // const frac: bigint = BigInt(Math.round((msFrac * 2 ** 32) / 1000));
1106
+ // return (seconds << 32n) | frac;
1107
+ // }
1108
+ //#endregion Functions
1109
+ // --------------------------------------------------------------------------------
1110
+ // Classes
1111
+ // --------------------------------------------------------------------------------
1112
+ //#region Classes
1113
+ /**
1114
+ * @todo
1115
+ */
1116
+ export class Structure {
1117
+ target;
1118
+ constructor(options) {
1119
+ this.target = options.target;
1120
+ }
1121
+ /**
1122
+ * @todo
1123
+ */
1124
+ fillBlocks(from, to, block) {
1125
+ throw new Error("Method not implemented.");
1126
+ }
1127
+ saveChanges() {
1128
+ throw new Error("Method not implemented.");
1129
+ }
1130
+ delete() {
1131
+ throw new Error("Method not implemented.");
1132
+ }
1133
+ expand(min, max) {
1134
+ throw new Error("Method not implemented.");
1135
+ }
1136
+ shrink(min, max) {
1137
+ throw new Error("Method not implemented.");
1138
+ }
1139
+ move(min, max) {
1140
+ throw new Error("Method not implemented.");
1141
+ }
1142
+ scale(scale) {
1143
+ throw new Error("Method not implemented.");
1144
+ }
1145
+ rotate(angle, axis = "y") {
1146
+ throw new Error("Method not implemented.");
1147
+ }
1148
+ mirror(axis) {
1149
+ throw new Error("Method not implemented.");
1150
+ }
1151
+ clear() {
1152
+ throw new Error("Method not implemented.");
1153
+ }
1154
+ clearSectionData(from, to) {
1155
+ throw new Error("Method not implemented.");
1156
+ }
1157
+ getSectionData(from, to) {
1158
+ throw new Error("Method not implemented.");
1159
+ }
1160
+ replaceSectionData(offset, data, options = {}) {
1161
+ throw new Error("Method not implemented.");
1162
+ }
1163
+ exportPrismarineNBT() {
1164
+ throw new Error("Method not implemented.");
1165
+ }
1166
+ }
1167
+ //#endregion Classes
1168
+ //# sourceMappingURL=LevelUtils.js.map