mcbe-leveldb 1.18.0-jsonly → 1.18.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.ts ADDED
@@ -0,0 +1,4750 @@
1
+ import NBT from "prismarine-nbt";
2
+ import BiomeData from "./__biome_data__.ts";
3
+ import type { LevelDB } from "@8crafter/leveldb-zlib";
4
+ import type { NBTSchemas } from "./nbtSchemas.ts";
5
+ import type { LooseAutocomplete, Range } from "./types.js";
6
+ import { toLongParts } from "./SNBTUtils.ts";
7
+
8
+ //#region Local Constants
9
+
10
+ const DEBUG = false;
11
+ const fileNameEncodeCharacterRegExp = /[/:>?\\]/g;
12
+ const fileNameCharacterFilterRegExp = /[^a-zA-Z0-9-_+,-.;=@~/:>?\\]/g;
13
+
14
+ //#endregion
15
+
16
+ //#region Local Functions
17
+
18
+ /**
19
+ * A tuple of length 16.
20
+ *
21
+ * @template T The type of the elements in the tuple.
22
+ */
23
+ type TupleOfLength16<T> = [T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T];
24
+
25
+ function readInt16LE(buf: Uint8Array, offset: number): number {
26
+ const val = buf[offset]! | (buf[offset + 1]! << 8);
27
+ return val >= 0x8000 ? val - 0x10000 : val;
28
+ }
29
+
30
+ function writeInt16LE(value: number): Buffer<ArrayBuffer> {
31
+ const buf = Buffer.alloc(2);
32
+ buf.writeInt16LE(value, 0);
33
+ return buf;
34
+ }
35
+
36
+ function writeInt32LE(value: number): Buffer<ArrayBuffer> {
37
+ const buf = Buffer.alloc(4);
38
+ buf.writeInt32LE(value, 0);
39
+ return buf;
40
+ }
41
+
42
+ // export function readData3dValue(rawvalue: Uint8Array | null): {
43
+ // /**
44
+ // * A height map in the form [x][z].
45
+ // */
46
+ // heightMap: TupleOfLength16<TupleOfLength16<number>>;
47
+ // /**
48
+ // * A biome map in the form [x][y][z].
49
+ // */
50
+ // biomeMap: TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>>;
51
+ // } | null {
52
+ // if (!rawvalue) {
53
+ // return null;
54
+ // }
55
+
56
+ // // --- Height map (first 512 bytes -> 256 signed 16-bit ints) ---
57
+ // const heightMap: TupleOfLength16<TupleOfLength16<number>> = Array.from(
58
+ // { length: 16 },
59
+ // (): TupleOfLength16<number> => Array(16).fill(0) as TupleOfLength16<number>
60
+ // ) as TupleOfLength16<TupleOfLength16<number>>;
61
+ // for (let i: number = 0; i < 256; i++) {
62
+ // const val: number = readInt16LE(rawvalue, i * 2);
63
+ // const x: number = i % 16;
64
+ // const z: number = Math.floor(i / 16);
65
+ // heightMap[x]![z] = val;
66
+ // }
67
+
68
+ // // --- Biome subchunks (remaining bytes) ---
69
+ // const biomeBytes: Uint8Array<ArrayBuffer> = rawvalue.slice(512);
70
+ // let b: BiomePalette[] = readChunkBiomes(biomeBytes);
71
+
72
+ // // Validate Biome Data
73
+ // if (b.length === 0 || b[0]!.values === null) {
74
+ // throw new Error("Value does not contain at least one subchunk of biome data.");
75
+ // }
76
+
77
+ // // Enlarge list to length 24 if necessary
78
+ // if (b.length < 24) {
79
+ // while (b.length < 24) b.push({ values: null, palette: [] });
80
+ // }
81
+
82
+ // // Trim biome data
83
+ // if (b.length > 24) {
84
+ // if (b.slice(24).some((sub: BiomePalette): boolean => sub.values !== null)) {
85
+ // console.warn(`Trimming biome data from ${b.length} to 24 subchunks.`);
86
+ // }
87
+ // b = b.slice(0, 24);
88
+ // }
89
+
90
+ // // --- Fill biome_map [16][24*16][16] (x,y,z order) ---
91
+ // const biomeMap: TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>> = Array.from(
92
+ // { length: 16 },
93
+ // (): TupleOfLength16<TupleOfLength16<number>> =>
94
+ // Array.from({ length: 24 * 16 }, (): TupleOfLength16<number> => Array(16).fill(NaN) as TupleOfLength16<number>) as TupleOfLength16<
95
+ // TupleOfLength16<number>
96
+ // >
97
+ // ) as TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>>;
98
+
99
+ // const hasData: boolean[] = b.map((sub: BiomePalette): boolean => sub.values !== null);
100
+ // const ii: number[] = hasData
101
+ // .map((has: boolean, idx: number): number | null => (has ? idx + 1 : null))
102
+ // .filter((i: number | null): i is number => i !== null);
103
+
104
+ // for (const i of ii) {
105
+ // const bb: BiomePalette = b[i - 1]!;
106
+ // if (!bb.values) continue;
107
+
108
+ // for (let u: number = 0; u < 4096; u++) {
109
+ // const val: number = bb.palette[bb.values[u]! - 1]!; // R is 1-based
110
+ // const x: number = u % 16;
111
+ // const z: number = Math.floor(u / 16) % 16;
112
+ // const y: number = Math.floor(u / 256);
113
+ // biomeMap[x]![16 * (i - 1) + y]![z] = val;
114
+ // }
115
+ // }
116
+
117
+ // // Fill missing subchunks by copying top of last data subchunk
118
+ // const iMax: number = Math.max(...ii);
119
+ // if (iMax < 24) {
120
+ // const y: number = 16 * iMax - 1;
121
+ // for (let yy: number = y + 1; yy < 24 * 16; yy++) {
122
+ // for (let x: number = 0; x < 16; x++) {
123
+ // for (let z: number = 0; z < 16; z++) {
124
+ // biomeMap[x]![yy]![z] = biomeMap[x]![y]![z]!;
125
+ // }
126
+ // }
127
+ // }
128
+ // }
129
+
130
+ // return { heightMap, biomeMap };
131
+ // }
132
+ /**
133
+ * Reads the value of the Data3D content type.
134
+ *
135
+ * @param rawvalue The raw value to read.
136
+ * @returns The height map and biome map.
137
+ */
138
+ export function readData3dValue(rawvalue: Uint8Array | null): {
139
+ /**
140
+ * A height map in the form [x][z].
141
+ */
142
+ heightMap: TupleOfLength16<TupleOfLength16<number>>;
143
+ /**
144
+ * A biome map as an array of 24 or more BiomePalette objects.
145
+ */
146
+ biomes: BiomePalette[];
147
+ } | null {
148
+ if (!rawvalue) {
149
+ return null;
150
+ }
151
+
152
+ // --- Height map (first 512 bytes -> 256 signed 16-bit ints) ---
153
+ const heightMap: TupleOfLength16<TupleOfLength16<number>> = Array.from(
154
+ { length: 16 },
155
+ (): TupleOfLength16<number> => Array(16).fill(0) as TupleOfLength16<number>
156
+ ) as TupleOfLength16<TupleOfLength16<number>>;
157
+
158
+ for (let i: number = 0; i < 256; i++) {
159
+ const val: number = readInt16LE(rawvalue, i * 2);
160
+ const x: number = i % 16;
161
+ const z: number = Math.floor(i / 16);
162
+ heightMap[x]![z] = val;
163
+ }
164
+
165
+ // --- Biome subchunks (remaining bytes) ---
166
+ const biomeBytes: Uint8Array = rawvalue.slice(512);
167
+ let biomes: BiomePalette[] = readChunkBiomes(biomeBytes);
168
+
169
+ // Validate Biome Data
170
+ if (biomes.length === 0 || biomes[0]!.values === null) {
171
+ throw new Error("Value does not contain at least one subchunk of biome data.");
172
+ }
173
+
174
+ // Enlarge list to length 24 if necessary
175
+ if (biomes.length < 24) {
176
+ while (biomes.length < 24) {
177
+ biomes.push({ values: null, palette: [] });
178
+ }
179
+ }
180
+
181
+ // Trim biome data (DISABLED (so that when increasing the height limits, it still works properly))
182
+ // if (biomeMap.length > 24) {
183
+ // if (biomeMap.slice(24).some((sub: BiomePalette): boolean => sub.values !== null)) {
184
+ // console.warn(`Trimming biome data from ${biomeMap.length} to 24 subchunks.`);
185
+ // }
186
+ // biomeMap = biomeMap.slice(0, 24);
187
+ // }
188
+
189
+ return { heightMap, biomes };
190
+ }
191
+
192
+ /**
193
+ * Writes the value of the Data3D content type.
194
+ *
195
+ * @param heightMap A height map in the form [x][z].
196
+ * @param biomes A biome map as an array of 24 or more BiomePalette objects.
197
+ * @returns The raw value to write.
198
+ */
199
+ export function writeData3DValue(heightMap: TupleOfLength16<TupleOfLength16<number>>, biomes: BiomePalette[]): Buffer<ArrayBuffer> {
200
+ // heightMap is 16x16, flatten to 256 shorts
201
+ const flatHeight: number[] = heightMap.flatMap((v: TupleOfLength16<number>, i: number): number[] =>
202
+ v.map((_v2: number, i2: number): number => heightMap[i2]![i]!)
203
+ );
204
+
205
+ // Write height map (512 bytes)
206
+ const heightBufs: Buffer[] = flatHeight.map((v: number): Buffer<ArrayBuffer> => writeInt16LE(v));
207
+ const heightBuf: Buffer<ArrayBuffer> = Buffer.concat(heightBufs);
208
+
209
+ // Write biome data
210
+ const biomeBuf: Buffer<ArrayBuffer> = writeChunkBiomes(biomes);
211
+
212
+ // Combine
213
+ return Buffer.concat([heightBuf, biomeBuf]);
214
+ }
215
+
216
+ /**
217
+ * Reads chunk biome data from a buffer.
218
+ *
219
+ * @param rawValue The raw value to read.
220
+ * @returns The biome data.
221
+ *
222
+ * @internal
223
+ */
224
+ function readChunkBiomes(rawValue: Uint8Array): BiomePalette[] {
225
+ let p: number = 0;
226
+ const end: number = rawValue.length;
227
+ const result: BiomePalette[] = [];
228
+
229
+ while (p < end) {
230
+ const { values, isPersistent, paletteSize, newOffset } = readSubchunkPaletteIds(rawValue, p, end);
231
+ p = newOffset;
232
+
233
+ if (values === null) {
234
+ result.push({ values: null, palette: [] });
235
+ continue;
236
+ }
237
+
238
+ if (isPersistent) {
239
+ throw new Error("Biome palette does not have runtime ids.", { cause: result.length });
240
+ }
241
+
242
+ if (end - p < paletteSize * 4) {
243
+ throw new Error("Subchunk biome palette is truncated.");
244
+ }
245
+
246
+ const palette: number[] = [];
247
+ for (let i: number = 0; i < paletteSize; i++) {
248
+ const val: number = rawValue[p]! | (rawValue[p + 1]! << 8) | (rawValue[p + 2]! << 16) | (rawValue[p + 3]! << 24);
249
+ palette.push(val);
250
+ p += 4;
251
+ }
252
+
253
+ result.push({ values, palette });
254
+ }
255
+
256
+ return result;
257
+ }
258
+
259
+ /**
260
+ * Writes the chunk biome data from a BiomePalette array to a buffer.
261
+ *
262
+ * @param biomes The biome data to write.
263
+ * @returns The resulting buffer.
264
+ *
265
+ * @internal
266
+ */
267
+ function writeChunkBiomes(biomes: BiomePalette[]): Buffer<ArrayBuffer> {
268
+ const buffers: Buffer<ArrayBuffer>[] = [];
269
+
270
+ for (const bb of biomes) {
271
+ if (!bb || bb.values === null /* || bb.values.length === 0 */) continue; // NULL case in R
272
+ const { values, palette } = bb;
273
+
274
+ // --- Write subchunk palette ids (bitpacked) ---
275
+ const idsBuf: Buffer<ArrayBuffer> = writeSubchunkPaletteIds(values!, palette.length);
276
+
277
+ // --- Don't write palette length if the palette is empty ---
278
+ if (!palette.length) {
279
+ buffers.push(idsBuf);
280
+ continue;
281
+ }
282
+
283
+ // --- Write palette size ---
284
+ const paletteSizeBuf: Buffer<ArrayBuffer> = writeInt32LE(palette.length);
285
+
286
+ // --- Write palette values ---
287
+ const paletteBufs: Buffer<ArrayBuffer>[] = palette.map((v: number): Buffer<ArrayBuffer> => writeInt32LE(v));
288
+
289
+ buffers.push(idsBuf, paletteSizeBuf, ...paletteBufs);
290
+ }
291
+
292
+ return Buffer.concat(buffers);
293
+ }
294
+
295
+ function readSubchunkPaletteIds(
296
+ buffer: Uint8Array,
297
+ offset: number,
298
+ end: number
299
+ ): {
300
+ values: number[] | null;
301
+ isPersistent: boolean;
302
+ paletteSize: number;
303
+ newOffset: number;
304
+ } {
305
+ let p = offset;
306
+
307
+ if (end - p < 1) {
308
+ throw new Error("Subchunk biome error: not enough data for flags.");
309
+ }
310
+
311
+ const flags = buffer[p++]!;
312
+ const isPersistent = (flags & 1) === 0;
313
+ const bitsPerBlock = flags >> 1;
314
+
315
+ // Special case: palette copy
316
+ if (bitsPerBlock === 127) {
317
+ return { values: null, isPersistent, paletteSize: 0, newOffset: p };
318
+ }
319
+
320
+ const values = new Array(4096);
321
+
322
+ if (bitsPerBlock > 0) {
323
+ const blocksPerWord = 32 / bitsPerBlock;
324
+ const wordCount = Math.floor(4095 / blocksPerWord) + 1;
325
+ const mask = (1 << bitsPerBlock) - 1;
326
+
327
+ // console.warn(
328
+ // "blocksPerWord:",
329
+ // blocksPerWord,
330
+ // "wordCount:",
331
+ // wordCount,
332
+ // "bitsPerBlock:",
333
+ // bitsPerBlock,
334
+ // "mask:",
335
+ // mask,
336
+ // "p:",
337
+ // p,
338
+ // "end:",
339
+ // end,
340
+ // "end-p:",
341
+ // end - p,
342
+ // "4*wordCount:",
343
+ // 4 * wordCount,
344
+ // "flags:",
345
+ // flags,
346
+ // "isPersistent:",
347
+ // isPersistent,
348
+ // "offset:",
349
+ // offset
350
+ // );
351
+
352
+ if (end - p < 4 * wordCount) {
353
+ throw new Error("Subchunk biome error: not enough data for block words.");
354
+ }
355
+ // const originalP = p;
356
+
357
+ let u = 0;
358
+ for (let j = 0; j < wordCount; j++) {
359
+ let temp = buffer[p]! | (buffer[p + 1]! << 8) | (buffer[p + 2]! << 16) | (buffer[p + 3]! << 24);
360
+ p += 4;
361
+
362
+ for (let k = 0; k < blocksPerWord && u < 4096; k++) {
363
+ // const x = (u >> 8) & 0xf;
364
+ // const z = (u >> 4) & 0xf;
365
+ // const y = u & 0xf;
366
+
367
+ values[u] = temp & mask;
368
+ temp >>= bitsPerBlock;
369
+ u++;
370
+ }
371
+ }
372
+
373
+ if (end - p < 4) {
374
+ throw new Error("Subchunk biome error: missing palette size.");
375
+ }
376
+
377
+ const paletteSize = buffer[p]! | (buffer[p + 1]! << 8) | (buffer[p + 2]! << 16) | (buffer[p + 3]! << 24);
378
+ p += 4;
379
+
380
+ // UNDONE: This does not actually restore the original data.
381
+ // Attempt to repair corrupted value data from versions <=v1.9.0 of the module.
382
+ // if (blocksPerWord !== Math.floor(blocksPerWord) && end - originalP < 4 * (Math.floor(4095 / Math.floor(blocksPerWord)) + 1)) {
383
+ // for (let u = 0; u < 4096; u++) {
384
+ // const v = values[u]!;
385
+ // const repairedValue: number = Math.floor(v / 2);
386
+ // values[u] = repairedValue;
387
+ // }
388
+ // }
389
+
390
+ return { values, isPersistent, paletteSize, newOffset: p };
391
+ } else {
392
+ // bitsPerBlock == 0 -> everything is ID 1
393
+ for (let u = 0; u < 4096; u++) {
394
+ values[u] = 0;
395
+ }
396
+ return { values, isPersistent, paletteSize: 1, newOffset: p };
397
+ }
398
+ }
399
+
400
+ function writeSubchunkPaletteIds(values: number[], paletteSize: number): Buffer<ArrayBuffer> {
401
+ const blockCount: number = values.length; // usually 16*16*16 = 4096
402
+ const bitsPerBlockOriginal: number = Math.max(1, Math.ceil(Math.log2(paletteSize)));
403
+ const bitsPerBlockDivisors: number[] = [1, 2, 4, 8, 16, 32];
404
+
405
+ const bitsPerBlock: number = paletteSize === 0 ? 127 : (bitsPerBlockDivisors.find((d: number): boolean => d >= bitsPerBlockOriginal) ?? 32);
406
+
407
+ // console.log(bitsPerBlock);
408
+
409
+ const wordsPerBlock: number = paletteSize === 0 ? 0 : Math.ceil((blockCount * bitsPerBlock) / 32);
410
+ const words = new Uint32Array(wordsPerBlock);
411
+
412
+ // let bitIndex: number = 0;
413
+ // for (const v of values) {
414
+ // let idx: number = v >>> 0; // ensure unsigned
415
+ // for (let i: number = 0; i < bitsPerBlock; i++) {
416
+ // if (idx & (1 << i)) {
417
+ // const wordIndex: number = Math.floor(bitIndex / 32);
418
+ // const bitOffset: number = bitIndex % 32;
419
+ // words[wordIndex]! |= 1 << bitOffset;
420
+ // }
421
+ // bitIndex++;
422
+ // }
423
+ // }
424
+ let bitIndex = 0;
425
+ for (let i = 0; i < values.length; i++) {
426
+ let val = values[i]! & ((1 << bitsPerBlock) - 1); // mask to bpe bits
427
+ const wordIndex = Math.floor(bitIndex / 32);
428
+ const bitOffset = bitIndex % 32;
429
+ words[wordIndex]! |= val << bitOffset;
430
+ if (bitOffset + bitsPerBlock > 32) {
431
+ // spill over into next word
432
+ words[wordIndex + 1]! |= val >>> (32 - bitOffset);
433
+ }
434
+ bitIndex += bitsPerBlock;
435
+ }
436
+ // words.forEach((val, i) => {
437
+ // words[i] = (-val - 1) & 0xffffffff;
438
+ // });
439
+
440
+ // --- Write header byte ---
441
+ const header: Buffer<ArrayBuffer> = Buffer.alloc(1);
442
+ header.writeUInt8((bitsPerBlock << 1) | 1, 0);
443
+
444
+ // --- Write packed data ---
445
+ const packed: Buffer<ArrayBuffer> = Buffer.alloc(words.length * 4);
446
+ for (let i: number = 0; i < words.length; i++) {
447
+ packed.writeUInt32LE(words[i]!, i * 4);
448
+ }
449
+
450
+ return Buffer.concat([header, packed]);
451
+ }
452
+
453
+ function fakeAssertType<T>(value: unknown): asserts value is T {
454
+ void value;
455
+ return;
456
+ }
457
+
458
+ //#endregion
459
+
460
+ // --------------------------------------------------------------------------------
461
+ // Constants
462
+ // --------------------------------------------------------------------------------
463
+
464
+ //#region Constants
465
+
466
+ /**
467
+ * The list of Minecraft dimensions.
468
+ */
469
+ export const dimensions = ["overworld", "nether", "the_end"] as const;
470
+
471
+ /**
472
+ * The list of Minecraft game modes, with their indices corresponding to their numeric gamemode IDs.
473
+ */
474
+ export const gameModes = ["survival", "creative", "adventure", , , "default", "spectator"] as const;
475
+
476
+ /**
477
+ * The content types for LevelDB entries.
478
+ */
479
+ export const DBEntryContentTypes = [
480
+ // Biome Linked
481
+ "Data3D",
482
+ "Version",
483
+ "Data2D",
484
+ "Data2DLegacy",
485
+ "SubChunkPrefix",
486
+ "LegacyTerrain",
487
+ "BlockEntity",
488
+ "Entity",
489
+ "PendingTicks",
490
+ "LegacyBlockExtraData",
491
+ "BiomeState",
492
+ "FinalizedState",
493
+ "ConversionData",
494
+ "BorderBlocks",
495
+ "HardcodedSpawners",
496
+ "RandomTicks",
497
+ "Checksums",
498
+ "GenerationSeed",
499
+ "GeneratedPreCavesAndCliffsBlending",
500
+ "BlendingBiomeHeight",
501
+ "MetaDataHash",
502
+ "BlendingData",
503
+ "ActorDigestVersion",
504
+ "LegacyVersion",
505
+ "AABBVolumes", // TO-DO: Figure out how to parse this, it seems to have data for trial chambers and trail ruins.
506
+ // Village
507
+ "VillageDwellers",
508
+ "VillageInfo",
509
+ "VillagePOI",
510
+ "VillagePlayers",
511
+ "VillageRaid",
512
+ // Standalone
513
+ "Player",
514
+ "PlayerClient",
515
+ "ActorPrefix",
516
+ "ChunkLoadedRequest",
517
+ "Digest",
518
+ "Map",
519
+ "Portals",
520
+ "SchedulerWT",
521
+ "StructureTemplate",
522
+ "TickingArea",
523
+ "FlatWorldLayers",
524
+ "LegacyOverworld",
525
+ "LegacyNether",
526
+ "LegacyTheEnd",
527
+ "MVillages",
528
+ "Villages",
529
+ "LevelSpawnWasFixed",
530
+ "PositionTrackingDB",
531
+ "PositionTrackingLastId",
532
+ "Scoreboard",
533
+ "Overworld",
534
+ "Nether",
535
+ "TheEnd",
536
+ "CustomDimension",
537
+ "AutonomousEntities",
538
+ "BiomeData",
539
+ "BiomeIdsTable",
540
+ "DimensionNameIdTable",
541
+ "MobEvents",
542
+ "DynamicProperties",
543
+ "LevelChunkMetaDataDictionary",
544
+ "RealmsStoriesData",
545
+ "LevelDat",
546
+ "WorldClocks",
547
+ // Dev Version
548
+ "ForcedWorldCorruption",
549
+ // Misc.
550
+ "Unknown",
551
+ ] as const;
552
+
553
+ /**
554
+ * Maps content types to grouping labels.
555
+ *
556
+ * Most content types are not grouped, only a few are.
557
+ */
558
+ export const DBEntryContentTypesGrouping = {
559
+ // Biome Linked
560
+ Data3D: "Data3D",
561
+ Version: "Version",
562
+ Data2D: "Data2D",
563
+ Data2DLegacy: "Data2DLegacy",
564
+ SubChunkPrefix: "SubChunkPrefix",
565
+ LegacyTerrain: "LegacyTerrain",
566
+ BlockEntity: "BlockEntity",
567
+ Entity: "Entity",
568
+ PendingTicks: "PendingTicks",
569
+ LegacyBlockExtraData: "LegacyBlockExtraData",
570
+ BiomeState: "BiomeState",
571
+ FinalizedState: "FinalizedState",
572
+ ConversionData: "ConversionData",
573
+ BorderBlocks: "BorderBlocks",
574
+ HardcodedSpawners: "HardcodedSpawners",
575
+ RandomTicks: "RandomTicks",
576
+ Checksums: "Checksums",
577
+ GenerationSeed: "GenerationSeed",
578
+ GeneratedPreCavesAndCliffsBlending: "GeneratedPreCavesAndCliffsBlending",
579
+ BlendingBiomeHeight: "BlendingBiomeHeight",
580
+ MetaDataHash: "MetaDataHash",
581
+ BlendingData: "BlendingData",
582
+ ActorDigestVersion: "ActorDigestVersion",
583
+ LegacyVersion: "LegacyVersion",
584
+ AABBVolumes: "AABBVolumes",
585
+ // Village
586
+ VillageDwellers: "VillageDwellers",
587
+ VillageInfo: "VillageInfo",
588
+ VillagePOI: "VillagePOI",
589
+ VillagePlayers: "VillagePlayers",
590
+ VillageRaid: "VillageRaid",
591
+ // Standalone
592
+ Player: "Player",
593
+ PlayerClient: "PlayerClient",
594
+ ActorPrefix: "ActorPrefix",
595
+ ChunkLoadedRequest: "ChunkLoadedRequest",
596
+ Digest: "Digest",
597
+ Map: "Map",
598
+ Portals: "Portals",
599
+ SchedulerWT: "SchedulerWT",
600
+ StructureTemplate: "StructureTemplate",
601
+ TickingArea: "TickingArea",
602
+ FlatWorldLayers: "FlatWorldLayers",
603
+ LegacyOverworld: "LegacyDimension",
604
+ LegacyNether: "LegacyDimension",
605
+ LegacyTheEnd: "LegacyDimension",
606
+ MVillages: "MVillages",
607
+ Villages: "Villages",
608
+ LevelSpawnWasFixed: "LevelSpawnWasFixed",
609
+ PositionTrackingDB: "PositionTrackingDB",
610
+ PositionTrackingLastId: "PositionTrackingLastId",
611
+ Scoreboard: "Scoreboard",
612
+ Overworld: "Dimension",
613
+ Nether: "Dimension",
614
+ TheEnd: "Dimension",
615
+ CustomDimension: "Dimension",
616
+ AutonomousEntities: "AutonomousEntities",
617
+ BiomeData: "BiomeData",
618
+ BiomeIdsTable: "IdsTable",
619
+ DimensionNameIdTable: "IdsTable",
620
+ MobEvents: "MobEvents",
621
+ DynamicProperties: "DynamicProperties",
622
+ LevelChunkMetaDataDictionary: "LevelChunkMetaDataDictionary",
623
+ RealmsStoriesData: "RealmsStoriesData",
624
+ LevelDat: "LevelDat",
625
+ WorldClocks: "WorldClocks",
626
+ // Dev Version
627
+ ForcedWorldCorruption: "ForcedWorldCorruption",
628
+ // Misc.
629
+ Unknown: "Unknown",
630
+ } as const satisfies Record<DBEntryContentType, string>;
631
+
632
+ // TODO: Look into the supposed `idcounts` LevelDB key that was supposedly in MCPE v0.13.0.
633
+ // TODO: Add support for the LegacyPlayer content type which is based on a number stored in `cilentid.txt`.
634
+ // TODO: Look into if LegacyVillageManager (may just be another name for MVillages, key is allegedly `VillageManager`) is a real content type.
635
+
636
+ /**
637
+ * The content type to format mapping for LevelDB entries.
638
+ */
639
+ export const entryContentTypeToFormatMap = {
640
+ /**
641
+ * The Data3D content type contains heightmap and biome data for 16x16x16 chunks of the world.
642
+ *
643
+ * @see {@link NBTSchemas.nbtSchemas.Data3D}
644
+ */
645
+ Data3D: {
646
+ /**
647
+ * The format type of the data.
648
+ */
649
+ type: "custom",
650
+ /**
651
+ * The format type that results from the {@link entryContentTypeToFormatMap.Data3D.parse | parse} method.
652
+ */
653
+ resultType: "JSONNBT",
654
+ /**
655
+ * The function to parse the data.
656
+ *
657
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
658
+ *
659
+ * @param data The data to parse, as a buffer.
660
+ * @returns The parsed data.
661
+ *
662
+ * @throws {Error} If the value does not contain at least one subchunk of biome data.
663
+ */
664
+ parse(data: Buffer): NBTSchemas.NBTSchemaTypes.Data3D {
665
+ const result = readData3dValue(data);
666
+ return {
667
+ type: "compound",
668
+ value: {
669
+ heightMap: {
670
+ type: "list",
671
+ value: {
672
+ type: "list",
673
+ value: result!.heightMap.map((row) => ({
674
+ type: "short",
675
+ value: row,
676
+ })),
677
+ },
678
+ },
679
+ biomes: {
680
+ type: "list",
681
+ value: {
682
+ type: "compound",
683
+ value: result!.biomes.map(
684
+ (subchunk: BiomePalette): NBTSchemas.NBTSchemaTypes.Data3D["value"]["biomes"]["value"]["value"][number] => ({
685
+ values: {
686
+ type: "list",
687
+ value:
688
+ subchunk.values ?
689
+ {
690
+ type: "int",
691
+ value: subchunk.values,
692
+ }
693
+ : ({
694
+ type: "end",
695
+ value: [],
696
+ } as any),
697
+ },
698
+ palette: {
699
+ type: "list",
700
+ value: {
701
+ type: "int",
702
+ value: subchunk.palette,
703
+ },
704
+ },
705
+ })
706
+ ),
707
+ },
708
+ },
709
+ },
710
+ };
711
+ },
712
+ /**
713
+ * The function to serialize the data.
714
+ *
715
+ * This result of this can be written directly to the file or LevelDB entry.
716
+ *
717
+ * @param data The data to serialize.
718
+ * @returns The serialized data, as a buffer.
719
+ */
720
+ serialize(data: NBTSchemas.NBTSchemaTypes.Data3D): Buffer<ArrayBuffer> {
721
+ return writeData3DValue(
722
+ data.value.heightMap.value.value.map((row: { type: "short"; value: number[] }): number[] => row.value) as any,
723
+ data.value.biomes.value.value.map(
724
+ (subchunk: NBTSchemas.NBTSchemaTypes.Data3D["value"]["biomes"]["value"]["value"][number]): BiomePalette => ({
725
+ palette: subchunk.palette.value.value,
726
+ values: !subchunk.values.value.value ? null : subchunk.values.value.value,
727
+ })
728
+ )
729
+ );
730
+ },
731
+ // TO-DO: Add a default value for this.
732
+ },
733
+ /**
734
+ * The version of a chunk.
735
+ *
736
+ * Deleting think key causes the game to completely regenerate the corresponding chunk.
737
+ *
738
+ * Possible values:
739
+ * - `20`: v1.16.100.52
740
+ * - `21`: v1.16.100.57
741
+ * - `22`: v1.16.210
742
+ * - `23`: v1.16.220.50 (+Caves & Cliffs Experimental Toggle)
743
+ * - `24`: v1.16.220.50 (+Caves & Cliffs Experimental Toggle) (internal/developer builds)
744
+ * - `25`: v1.16.230.50 (+Caves & Cliffs Experimental Toggle)
745
+ * - `26`: v1.16.230.50 (+Caves & Cliffs Experimental Toggle) (internal/developer builds)
746
+ * - `27`: v1.17.30.23 (+Caves & Cliffs Experimental Toggle)
747
+ * - `28`: v1.17.30.23 (+Caves & Cliffs Experimental Toggle) (internal/developer builds)
748
+ * - `29`: v1.17.30.25 (+Caves & Cliffs Experimental Toggle)
749
+ * - `30`: v1.17.30.25 (+Caves & Cliffs Experimental Toggle) (internal/developer builds)
750
+ * - `31`: v1.17.40.20 (+Caves & Cliffs Experimental Toggle)
751
+ * - `32`: v1.17.40.20 (+Caves & Cliffs Experimental Toggle) (internal/developer builds)
752
+ * - `33`: v1.18.0.20
753
+ * - `34`: v1.18.0.20 (internal/developer builds)
754
+ * - `35`: v1.18.0.22
755
+ * - `36`: v1.18.0.22 (internal/developer builds)
756
+ * - `37`: v1.18.0.24
757
+ * - `38`: v1.18.0.24 (internal/developer builds)
758
+ * - `39`: v1.18.0.25
759
+ * - `40`: v1.18.30 after upgrading entities to independent storage
760
+ * - `41`: v1.21.40
761
+ * - `42`: v1.21.120
762
+ *
763
+ * @since v1.16.100
764
+ */
765
+ Version: {
766
+ // https://github.com/reedacartwright/rbedrock/blob/c9040e803c4172a19507bc8f1278802ad0e6170a/R/chunk_version.R
767
+ // https://github.com/robofinch/Prismarine-Anchor/blob/6ce90fa2a27c5d81e6cc1cc376e91c757a80e321/crates/bedrock/bedrock-entries/src/enum_types/chunk_version.rs#L24
768
+ // https://github.com/reedacartwright/rbedrock/blob/c9040e803c4172a19507bc8f1278802ad0e6170a/man/ChunkVersion.Rd
769
+ // Value list: https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L98
770
+ /**
771
+ * The format type of the data.
772
+ */
773
+ type: "int",
774
+ /**
775
+ * How many bytes this integer is.
776
+ */
777
+ bytes: 1,
778
+ /**
779
+ * The endianness of the data.
780
+ */
781
+ format: "LE",
782
+ /**
783
+ * The signedness of the data.
784
+ */
785
+ signed: false,
786
+ /**
787
+ * The default value to use when initializing a new entry.
788
+ *
789
+ * Bytes:
790
+ * ```json
791
+ * [41]
792
+ * ```
793
+ */
794
+ defaultValue: Buffer.from([41]),
795
+ },
796
+ /**
797
+ * The Data2D content type contains heightmap and biome data for 16x128x16 chunks of the world.
798
+ *
799
+ * Unlike {@link entryContentTypeToFormatMap.Data3D | Data3D}, this only stores biome data for xz coordinates, so in this format all y coordinates have the same biome.
800
+ *
801
+ * Only used in versions < 1.18.0, and for worlds using the Old world type or using an older base game version.
802
+ */
803
+ Data2D: {
804
+ /**
805
+ * The format type of the data.
806
+ */
807
+ type: "custom",
808
+ /**
809
+ * The format type that results from the {@link entryContentTypeToFormatMap.Data2D.parse | parse} method.
810
+ */
811
+ resultType: "JSONNBT",
812
+ /**
813
+ * The function to parse the data.
814
+ *
815
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
816
+ *
817
+ * @param data The data to parse, as a buffer.
818
+ * @returns The parsed data.
819
+ */
820
+ parse(data: Buffer): NBTSchemas.NBTSchemaTypes.Data2D {
821
+ const formatVersion: NBTSchemas.NBTSchemaTypes.Data2D["value"]["version"]["value"] | -1 =
822
+ data.length === 768 ? 1
823
+ : data.length === 1024 ? 2
824
+ : -1;
825
+ if (formatVersion === -1) throw new Error(`Unknown Data2D format, expected 768 or 1024 bytes, got ${data.length} bytes`);
826
+
827
+ const heightMap: TupleOfLength16<TupleOfLength16<number>> = Array.from(
828
+ { length: 16 },
829
+ (): TupleOfLength16<number> => Array(16).fill(0) as TupleOfLength16<number>
830
+ ) as TupleOfLength16<TupleOfLength16<number>>;
831
+
832
+ for (let i = 0; i < 256; i++) {
833
+ const val: number = readInt16LE(data, i * 2);
834
+ const x: number = i % 16;
835
+ const z: number = Math.floor(i / 16);
836
+ heightMap[x]![z] = val;
837
+ }
838
+
839
+ const biomeData: TupleOfLength16<TupleOfLength16<number>> = Array.from(
840
+ { length: 16 },
841
+ (): TupleOfLength16<number> => Array(16).fill(0) as TupleOfLength16<number>
842
+ ) as TupleOfLength16<TupleOfLength16<number>>;
843
+
844
+ switch (formatVersion) {
845
+ case 1:
846
+ for (let i = 0; i < 256; i++) {
847
+ const val: number = data.readUInt8(512 + i);
848
+ const x: number = i % 16;
849
+ const z: number = Math.floor(i / 16);
850
+ biomeData[x]![z] = val;
851
+ }
852
+ return {
853
+ type: "compound",
854
+ value: {
855
+ version: {
856
+ type: "byte",
857
+ value: formatVersion,
858
+ },
859
+ heightMap: {
860
+ type: "list",
861
+ value: {
862
+ type: "list",
863
+ value: heightMap.map((row: TupleOfLength16<number>) => ({
864
+ type: "short",
865
+ value: row,
866
+ })),
867
+ },
868
+ },
869
+ biomeData: {
870
+ type: "list",
871
+ value: {
872
+ type: "list",
873
+ value: biomeData.map((row: TupleOfLength16<number>) => ({
874
+ type: "byte",
875
+ value: row,
876
+ })),
877
+ },
878
+ },
879
+ },
880
+ };
881
+ case 2:
882
+ for (let i = 0; i < 256; i++) {
883
+ const val: number = data.readUInt16LE(512 + i * 2);
884
+ const x: number = i % 16;
885
+ const z: number = Math.floor(i / 16);
886
+ biomeData[x]![z] = val;
887
+ }
888
+ return {
889
+ type: "compound",
890
+ value: {
891
+ version: {
892
+ type: "byte",
893
+ value: formatVersion,
894
+ },
895
+ heightMap: {
896
+ type: "list",
897
+ value: {
898
+ type: "list",
899
+ value: heightMap.map((row: TupleOfLength16<number>) => ({
900
+ type: "short",
901
+ value: row,
902
+ })),
903
+ },
904
+ },
905
+ biomeData: {
906
+ type: "list",
907
+ value: {
908
+ type: "list",
909
+ value: biomeData.map((row: TupleOfLength16<number>) => ({
910
+ type: "short",
911
+ value: row,
912
+ })),
913
+ },
914
+ },
915
+ },
916
+ };
917
+ default:
918
+ throw new Error(`Missing parser for Data2D format: ${formatVersion}`);
919
+ }
920
+ },
921
+ /**
922
+ * The function to serialize the data.
923
+ *
924
+ * This result of this can be written directly to the file or LevelDB entry.
925
+ *
926
+ * @param data The data to serialize.
927
+ * @returns The serialized data, as a buffer.
928
+ */
929
+ serialize(data: NBTSchemas.NBTSchemaTypes.Data2D): Buffer<ArrayBuffer> {
930
+ const formatVersion = data.value.version.value;
931
+ const heightMap = data.value.heightMap.value.value;
932
+ const biomeData = data.value.biomeData.value.value;
933
+
934
+ switch (formatVersion) {
935
+ case 1: {
936
+ const buffer: Buffer<ArrayBuffer> = Buffer.alloc(512 + 256);
937
+
938
+ for (let z = 0; z < 16; z++) {
939
+ for (let x: number = 0; x < 16; x++) {
940
+ const i: number = z * 16 + x;
941
+ const val: number = heightMap[x]!.value[z]!;
942
+ buffer.writeInt16LE(val, i * 2);
943
+ }
944
+ }
945
+
946
+ for (let z = 0; z < 16; z++) {
947
+ for (let x: number = 0; x < 16; x++) {
948
+ const i: number = z * 16 + x;
949
+ const val: number = biomeData[x]!.value[z]!;
950
+ buffer.writeUInt8(val, 512 + i);
951
+ }
952
+ }
953
+
954
+ return buffer;
955
+ }
956
+ case 2: {
957
+ const buffer: Buffer<ArrayBuffer> = Buffer.alloc(512 + 512);
958
+
959
+ for (let z = 0; z < 16; z++) {
960
+ for (let x: number = 0; x < 16; x++) {
961
+ const i: number = z * 16 + x;
962
+ const val: number = heightMap[x]!.value[z]!;
963
+ buffer.writeInt16LE(val, i * 2);
964
+ }
965
+ }
966
+
967
+ for (let z = 0; z < 16; z++) {
968
+ for (let x: number = 0; x < 16; x++) {
969
+ const i: number = z * 16 + x;
970
+ const val: number = biomeData[x]!.value[z]!;
971
+ buffer.writeUInt16LE(val, 512 + i * 2);
972
+ }
973
+ }
974
+
975
+ return buffer;
976
+ }
977
+ default:
978
+ throw new TypeError(`Unknown Data2D format: ${formatVersion}`);
979
+ }
980
+ },
981
+ },
982
+ /**
983
+ * The Data2D content type contains heightmap and biome data for 16x128x16 chunks of the world.
984
+ *
985
+ * Unlike {@link entryContentTypeToFormatMap.Data3D | Data3D}, this only stores biome data for xz coordinates, so in this format all y coordinates have the same biome.
986
+ *
987
+ * Unlike both {@link entryContentTypeToFormatMap.Data3D | Data3D} and {@link entryContentTypeToFormatMap.Data2D | Data2D}, this also stores biome color data.
988
+ *
989
+ * @deprecated Only used in versions < 1.0.0.
990
+ */
991
+ Data2DLegacy: {
992
+ /**
993
+ * The format type of the data.
994
+ */
995
+ type: "custom",
996
+ /**
997
+ * The format type that results from the {@link entryContentTypeToFormatMap.Data2DLegacy.parse | parse} method.
998
+ */
999
+ resultType: "JSONNBT",
1000
+ /**
1001
+ * The function to parse the data.
1002
+ *
1003
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
1004
+ *
1005
+ * @param data The data to parse, as a buffer.
1006
+ * @returns The parsed data.
1007
+ */
1008
+ parse(data: Buffer): NBTSchemas.NBTSchemaTypes.Data2DLegacy {
1009
+ const heightMap: TupleOfLength16<TupleOfLength16<number>> = Array.from(
1010
+ { length: 16 },
1011
+ (): TupleOfLength16<number> => Array(16).fill(0) as TupleOfLength16<number>
1012
+ ) as TupleOfLength16<TupleOfLength16<number>>;
1013
+
1014
+ for (let i = 0; i < 256; i++) {
1015
+ const val: number = readInt16LE(data, i * 2);
1016
+ const x: number = i % 16;
1017
+ const z: number = Math.floor(i / 16);
1018
+ heightMap[x]![z] = val;
1019
+ }
1020
+
1021
+ const biomeData: TupleOfLength16<TupleOfLength16<[biomeId: number, red: number, green: number, blue: number]>> = Array.from(
1022
+ { length: 16 },
1023
+ (): TupleOfLength16<[biomeId: number, red: number, green: number, blue: number]> =>
1024
+ Array(16).fill(0) as TupleOfLength16<[biomeId: number, red: number, green: number, blue: number]>
1025
+ ) as TupleOfLength16<TupleOfLength16<[biomeId: number, red: number, green: number, blue: number]>>;
1026
+
1027
+ for (let i = 0; i < 256; i++) {
1028
+ const vals: [biomeId: number, red: number, green: number, blue: number] = [
1029
+ data.readUInt8(512 + i * 4),
1030
+ data.readUInt8(512 + i * 4 + 1),
1031
+ data.readUInt8(512 + i * 4 + 2),
1032
+ data.readUInt8(512 + i * 4 + 3),
1033
+ ];
1034
+ const x: number = i % 16;
1035
+ const z: number = Math.floor(i / 16);
1036
+ biomeData[x]![z] = vals;
1037
+ }
1038
+
1039
+ return {
1040
+ type: "compound",
1041
+ value: {
1042
+ heightMap: {
1043
+ type: "list",
1044
+ value: {
1045
+ type: "list",
1046
+ value: heightMap.map((row: TupleOfLength16<number>) => ({
1047
+ type: "short",
1048
+ value: row,
1049
+ })),
1050
+ },
1051
+ },
1052
+ biomeData: {
1053
+ type: "list",
1054
+ value: {
1055
+ type: "list",
1056
+ value: biomeData.map((row: TupleOfLength16<[biomeId: number, red: number, green: number, blue: number]>) => ({
1057
+ type: "list",
1058
+ value: row.map((val: [biomeId: number, red: number, green: number, blue: number]) => ({
1059
+ type: "byte",
1060
+ value: val,
1061
+ })),
1062
+ })),
1063
+ },
1064
+ },
1065
+ },
1066
+ };
1067
+ },
1068
+ /**
1069
+ * The function to serialize the data.
1070
+ *
1071
+ * This result of this can be written directly to the file or LevelDB entry.
1072
+ *
1073
+ * @param data The data to serialize.
1074
+ * @returns The serialized data, as a buffer.
1075
+ */
1076
+ serialize(data: NBTSchemas.NBTSchemaTypes.Data2DLegacy): Buffer<ArrayBuffer> {
1077
+ const heightMap = data.value.heightMap.value.value;
1078
+ const biomeData = data.value.biomeData.value.value;
1079
+
1080
+ const buffer: Buffer<ArrayBuffer> = Buffer.alloc(512 + 1024);
1081
+
1082
+ for (let z = 0; z < 16; z++) {
1083
+ for (let x: number = 0; x < 16; x++) {
1084
+ const i: number = z * 16 + x;
1085
+ const val: number = heightMap[x]!.value[z]!;
1086
+ buffer.writeInt16LE(val, i * 2);
1087
+ }
1088
+ }
1089
+
1090
+ for (let z = 0; z < 16; z++) {
1091
+ for (let x: number = 0; x < 16; x++) {
1092
+ const i: number = z * 16 + x;
1093
+ const vals: [biomeId: number, red: number, green: number, blue: number] = biomeData[x]!.value[z]!.value;
1094
+ vals.forEach((val: number, index: number): void => void buffer.writeUInt8(val, 512 + i * 4 + index));
1095
+ }
1096
+ }
1097
+
1098
+ return buffer;
1099
+ },
1100
+ },
1101
+ /**
1102
+ * The SubChunkPrefix content type contains block data for 16x16x16 subchunks of the world.
1103
+ *
1104
+ * In older versions, it may also contain sky and block light data.
1105
+ *
1106
+ * @see {@link NBTSchemas.nbtSchemas.SubChunkPrefix}
1107
+ */
1108
+ SubChunkPrefix: {
1109
+ /**
1110
+ * The format type of the data.
1111
+ */
1112
+ type: "custom",
1113
+ /**
1114
+ * The format type that results from the {@link entryContentTypeToFormatMap.SubChunkPrefix.parse | parse} method.
1115
+ */
1116
+ resultType: "JSONNBT",
1117
+ /**
1118
+ * The function to parse the data.
1119
+ *
1120
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
1121
+ *
1122
+ * @param data The data to parse, as a buffer.
1123
+ * @returns A promise that resolves with the parsed data.
1124
+ *
1125
+ * @throws {Error} If the SubChunkPrefix version is unknown.
1126
+ * @throws {Error} If the storage version is unknown.
1127
+ */
1128
+ async parse(data: Buffer): Promise<NBTSchemas.NBTSchemaTypes.SubChunkPrefix> {
1129
+ let currentOffset: number = 0;
1130
+ /**
1131
+ * The version of the SubChunkPrefix.
1132
+ *
1133
+ * Should be `0x00`/`0x02`/`0x03`/`0x04`/`0x05`/`0x06`/`0x07` (1.0.0 <= x < 1.2.13), `0x01` (beta 1.2.13.5), `0x08` (1.2.13 <= x < 1.18.0), or `0x09` (1.18.0 <= x).
1134
+ *
1135
+ * @todo Add handling for `0x00`, `0x02`, `0x03`, `0x04`, `0x05`, `0x06`, and `0x07`.
1136
+ */
1137
+ const version: 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09 = data[currentOffset++]! as any;
1138
+ if (![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09].includes(version)) throw new Error(`Unknown SubChunkPrefix version: ${version}`);
1139
+ if (version === 0x00 || version === 0x02 || version === 0x03 || version === 0x04 || version === 0x05 || version === 0x06 || version === 0x07) {
1140
+ const block_ids: number[] = [...data.subarray(currentOffset, currentOffset + 4096)];
1141
+ currentOffset += 4096;
1142
+
1143
+ function unpackNibbleArray(byteCount: number): Range<0, 15>[] {
1144
+ const slice = data.subarray(currentOffset, currentOffset + byteCount);
1145
+ currentOffset += byteCount;
1146
+
1147
+ return [...slice].flatMap((n: number): number[] => [n >> 4, n & 0x0f]) as Range<0, 15>[];
1148
+ }
1149
+
1150
+ const block_data: Range<0, 15>[] = unpackNibbleArray(2048);
1151
+ const sky_light: Range<0, 15>[] | undefined = data.length - currentOffset > 0 ? unpackNibbleArray(2048) : undefined;
1152
+ const block_light: Range<0, 15>[] | undefined = data.length - currentOffset > 0 ? unpackNibbleArray(2048) : undefined;
1153
+ return NBT.comp({
1154
+ version: NBT.byte<0x00 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07>(version),
1155
+ block_data: { type: "list", value: { type: "byte", value: block_data } },
1156
+ block_ids: { type: "list", value: { type: "byte", value: block_ids } },
1157
+ ...(sky_light ? { sky_light: { type: "list", value: { type: "byte", value: sky_light } } } : {}),
1158
+ ...(block_light ? { block_light: { type: "list", value: { type: "byte", value: block_light } } } : {}),
1159
+ } as const satisfies NBTSchemas.NBTSchemaTypes.SubChunkPrefix_v0["value"]);
1160
+ }
1161
+ const layers: NBTSchemas.NBTSchemaTypes.SubChunkPrefixLayer["value"][] = [];
1162
+ /**
1163
+ * How many blocks are in each location (ex. 2 might mean here is a waterlog layer).
1164
+ */
1165
+ let numStorageBlocks: number = version === 0x01 ? 1 : data[currentOffset++]!;
1166
+ const subChunkIndex: number | undefined = version >= 0x09 ? data[currentOffset++] : undefined;
1167
+ for (let blockIndex: number = 0; blockIndex < numStorageBlocks; blockIndex++) {
1168
+ const storageVersion: number = data[currentOffset]!;
1169
+ if (
1170
+ !(
1171
+ storageVersion === 1 ||
1172
+ storageVersion === 2 ||
1173
+ storageVersion === 3 ||
1174
+ storageVersion === 4 ||
1175
+ storageVersion === 5 ||
1176
+ storageVersion === 6 ||
1177
+ storageVersion === 8 ||
1178
+ storageVersion === 16
1179
+ )
1180
+ )
1181
+ throw new Error(`Unknown storage version: ${storageVersion}`);
1182
+ currentOffset++;
1183
+ const bitsPerBlock: number = storageVersion >> 1;
1184
+ const blocksPerWord: number = Math.floor(32 / bitsPerBlock);
1185
+ const numints: number = Math.ceil(4096 / blocksPerWord);
1186
+ const blockDataOffset: number = currentOffset;
1187
+ let paletteOffset: number = blockDataOffset + 4 * numints;
1188
+ let psize: number = bitsPerBlock === 0 ? 0 : getInt32Val(data, paletteOffset);
1189
+ paletteOffset += Math.sign(bitsPerBlock) * 4;
1190
+ // const debugInfo = {
1191
+ // version,
1192
+ // blockIndex,
1193
+ // storageVersion,
1194
+ // bitsPerBlock,
1195
+ // blocksPerWord,
1196
+ // numints,
1197
+ // blockDataOffset,
1198
+ // paletteOffset,
1199
+ // psize,
1200
+ // // blockData,
1201
+ // // palette,
1202
+ // };
1203
+ const palette: {
1204
+ [paletteIndex: `${bigint}`]: NBTSchemas.NBTSchemaTypes.Block;
1205
+ } = {};
1206
+ for (let i: bigint = 0n; i < psize; i++) {
1207
+ // console.log(debugInfo);
1208
+ // appendFileSync("./test1.bin", JSON.stringify(debugInfo) + "\n");
1209
+ const result = (await NBT.parse(data.subarray(paletteOffset), "little")) as unknown as {
1210
+ parsed: NBTSchemas.NBTSchemaTypes.Block;
1211
+ type: NBT.NBTFormat;
1212
+ metadata: NBT.Metadata;
1213
+ };
1214
+ paletteOffset += result.metadata.size;
1215
+ palette[`${i}`] = result.parsed;
1216
+ // console.log(result);
1217
+ // appendFileSync("./test1.bin", JSON.stringify(result));
1218
+ }
1219
+ currentOffset = paletteOffset;
1220
+ const block_indices: number[] = [];
1221
+ for (let i: number = 0; i < 4096; i++) {
1222
+ const maskVal: number = getInt32Val(data, blockDataOffset + Math.floor(i / blocksPerWord) * 4);
1223
+ const blockVal: number = (maskVal >> ((i % blocksPerWord) * bitsPerBlock)) & ((1 << bitsPerBlock) - 1);
1224
+ // const blockType: DataTypes_Block | undefined = palette[`${blockVal as unknown as bigint}`];
1225
+ // if (!blockType && blockVal !== -1) throw new ReferenceError(`Invalid block palette index ${blockVal} for block ${i}`);
1226
+ /* const chunkOffset: Vector3 = {
1227
+ x: (i >> 8) & 0xf,
1228
+ y: (i >> 4) & 0xf,
1229
+ z: (i >> 0) & 0xf,
1230
+ }; */
1231
+ block_indices.push(blockVal);
1232
+ }
1233
+ layers.push({
1234
+ storageVersion: NBT.byte(storageVersion),
1235
+ palette: NBT.comp(palette),
1236
+ block_indices: { type: "list", value: { type: "int", value: block_indices } },
1237
+ });
1238
+ }
1239
+ if (version === 0x01) {
1240
+ return NBT.comp({
1241
+ version: NBT.byte<0x01>(version),
1242
+ layerCount: NBT.byte<1>(numStorageBlocks as 1),
1243
+ layers: { type: "list", value: NBT.comp(layers as [(typeof layers)[0]]) },
1244
+ } as const satisfies NBTSchemas.NBTSchemaTypes.SubChunkPrefix_v1["value"]);
1245
+ }
1246
+ return NBT.comp({
1247
+ version: NBT.byte<0x08 | 0x09>(version),
1248
+ layerCount: NBT.byte(numStorageBlocks),
1249
+ layers: { type: "list", value: NBT.comp(layers) },
1250
+ ...(version >= 0x09 ?
1251
+ {
1252
+ subChunkIndex: NBT.byte(subChunkIndex!),
1253
+ }
1254
+ : {}),
1255
+ } as const satisfies NBTSchemas.NBTSchemaTypes.SubChunkPrefix_v8["value"]);
1256
+ },
1257
+ /**
1258
+ * The function to serialize the data.
1259
+ *
1260
+ * This result of this can be written directly to the file or LevelDB entry.
1261
+ *
1262
+ * @param data The data to serialize.
1263
+ * @returns The serialized data, as a buffer.
1264
+ */
1265
+ serialize(data: NBTSchemas.NBTSchemaTypes.SubChunkPrefix): Buffer<ArrayBuffer> {
1266
+ if (![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09].includes(data.value.version.value))
1267
+ throw new Error(`Unsupported subchunk prefix version: ${data.value.version.value}`);
1268
+ switch (data.value.version.value) {
1269
+ case 0x00:
1270
+ case 0x02:
1271
+ case 0x03:
1272
+ case 0x04:
1273
+ case 0x05:
1274
+ case 0x06:
1275
+ case 0x07: {
1276
+ fakeAssertType<NBTSchemas.NBTSchemaTypes.SubChunkPrefix_v0>(data);
1277
+ function packNibbles(arr: number[]): number[] {
1278
+ return arr.reduce(
1279
+ (p: number[], v: number, i: number): number[] =>
1280
+ i % 2 === 0 ? [...p, v & 0x0f] : [...p.slice(0, -1), (p.at(-1)! << 4) | (v & 0x0f)],
1281
+ []
1282
+ );
1283
+ }
1284
+
1285
+ const buffer: Buffer<ArrayBuffer> = Buffer.from([data.value.version.value]);
1286
+ return Buffer.concat([
1287
+ buffer,
1288
+ Buffer.from(data.value.block_ids.value.value),
1289
+ Buffer.from(packNibbles(data.value.block_data.value.value)),
1290
+ ...(data.value.sky_light ? [Buffer.from(packNibbles(data.value.sky_light.value.value))] : []),
1291
+ ...(data.value.block_light ? [Buffer.from(packNibbles(data.value.block_light.value.value))] : []),
1292
+ ]);
1293
+ }
1294
+ case 0x01:
1295
+ case 0x08:
1296
+ case 0x09: {
1297
+ fakeAssertType<NBTSchemas.NBTSchemaTypes.SubChunkPrefix_v1 | NBTSchemas.NBTSchemaTypes.SubChunkPrefix_v8>(data);
1298
+ const buffer: Buffer<ArrayBuffer> = Buffer.from([
1299
+ data.value.version.value,
1300
+ ...(data.value.version.value >= 0x08 ? [data.value.layerCount.value] : []),
1301
+ ...(data.value.version.value >= 0x09 ? [(data as NBTSchemas.NBTSchemaTypes.SubChunkPrefix_v8).value.subChunkIndex!.value] : []),
1302
+ ]);
1303
+ const layerBuffers: Buffer<ArrayBuffer>[] = data.value.layers.value.value.map(
1304
+ (layer: NBTSchemas.NBTSchemaTypes.SubChunkPrefixLayer["value"]): Buffer<ArrayBuffer> => {
1305
+ const bitsPerBlock: number = layer.storageVersion.value >> 1;
1306
+ const blocksPerWord: number = Math.floor(32 / bitsPerBlock);
1307
+ const numints: number = Math.ceil(4096 / blocksPerWord);
1308
+ const bytes: number[] = [layer.storageVersion.value];
1309
+ const blockIndicesBuffer: Buffer<ArrayBuffer> = Buffer.alloc(Math.ceil(numints * 4));
1310
+ writeBlockIndices(blockIndicesBuffer, 0, layer.block_indices.value.value, bitsPerBlock, blocksPerWord);
1311
+ bytes.push(...blockIndicesBuffer);
1312
+ const paletteLengthBuffer: Buffer<ArrayBuffer> = Buffer.alloc(4);
1313
+ setInt32Val(paletteLengthBuffer, 0, Object.keys(layer.palette.value).length);
1314
+ bytes.push(...paletteLengthBuffer);
1315
+ const paletteKeys: `${bigint}`[] = (Object.keys(layer.palette.value) as `${bigint}`[]).sort(
1316
+ (a: `${bigint}`, b: `${bigint}`): number => Number(a) - Number(b)
1317
+ );
1318
+ for (let paletteIndex: number = 0; paletteIndex < paletteKeys.length; paletteIndex++) {
1319
+ const block: NBTSchemas.NBTSchemaTypes.Block = layer.palette.value[paletteKeys[paletteIndex]!]!;
1320
+ bytes.push(...NBT.writeUncompressed({ name: "", ...block }, "little"));
1321
+ }
1322
+ return Buffer.from(bytes);
1323
+ }
1324
+ );
1325
+ return Buffer.concat([buffer, ...layerBuffers]);
1326
+ }
1327
+ }
1328
+ },
1329
+ // TO-DO: Add a default value for this.
1330
+ },
1331
+ /**
1332
+ * The LegacyTerrain content type contains block, sky light, block light, dirty columns, and grass color data for 16x16x128 chunks of the world.
1333
+ *
1334
+ * @deprecated Only used in versions < 1.0.0.
1335
+ *
1336
+ * @see {@link NBTSchemas.nbtSchemas.LegacyTerrain}
1337
+ */
1338
+ LegacyTerrain: {
1339
+ /**
1340
+ * The format type of the data.
1341
+ */
1342
+ type: "custom",
1343
+ /**
1344
+ * The format type that results from the {@link entryContentTypeToFormatMap.LegacyTerrain.parse | parse} method.
1345
+ */
1346
+ resultType: "JSONNBT",
1347
+ /**
1348
+ * The function to parse the data.
1349
+ *
1350
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
1351
+ *
1352
+ * @param data The data to parse, as a buffer.
1353
+ * @returns The parsed data.
1354
+ */
1355
+ parse(data: Buffer): NBTSchemas.NBTSchemaTypes.LegacyTerrain {
1356
+ let currentOffset = 0;
1357
+
1358
+ const block_ids: number[] = [...data.subarray(currentOffset, currentOffset + 32768)];
1359
+ currentOffset += 32768;
1360
+
1361
+ function unpackNibbleArray(byteCount: number): Range<0, 15>[] {
1362
+ const slice = data.subarray(currentOffset, currentOffset + byteCount);
1363
+ currentOffset += byteCount;
1364
+
1365
+ return [...slice].flatMap((n: number): number[] => [n >> 4, n & 0x0f]) as Range<0, 15>[];
1366
+ }
1367
+
1368
+ const block_data: Range<0, 15>[] = unpackNibbleArray(16384);
1369
+
1370
+ const sky_light: Range<0, 15>[] = unpackNibbleArray(16384);
1371
+
1372
+ const block_light: Range<0, 15>[] = unpackNibbleArray(16384);
1373
+
1374
+ const height_map: number[] = [...data.subarray(currentOffset, currentOffset + 256)];
1375
+ currentOffset += 256;
1376
+
1377
+ const grass_color: number[] = [...data.subarray(currentOffset, currentOffset + 1024)];
1378
+ currentOffset += 1024;
1379
+
1380
+ return NBT.comp({
1381
+ block_ids: { type: "list", value: { type: "byte", value: block_ids } },
1382
+ block_data: { type: "list", value: { type: "byte", value: block_data } },
1383
+ sky_light: { type: "list", value: { type: "byte", value: sky_light } },
1384
+ block_light: { type: "list", value: { type: "byte", value: block_light } },
1385
+ height_map: { type: "list", value: { type: "byte", value: height_map } },
1386
+ grass_color: { type: "list", value: { type: "byte", value: grass_color } },
1387
+ } as const satisfies NBTSchemas.NBTSchemaTypes.LegacyTerrain["value"]);
1388
+ },
1389
+ /**
1390
+ * The function to serialize the data.
1391
+ *
1392
+ * This result of this can be written directly to the file or LevelDB entry.
1393
+ *
1394
+ * @param data The data to serialize.
1395
+ * @returns The serialized data, as a buffer.
1396
+ */
1397
+ serialize(data: NBTSchemas.NBTSchemaTypes.LegacyTerrain): Buffer<ArrayBuffer> {
1398
+ function packNibbles(arr: number[]): number[] {
1399
+ return arr.reduce(
1400
+ (p: number[], v: number, i: number): number[] => (i % 2 === 0 ? [...p, v & 0x0f] : [...p.slice(0, -1), (p.at(-1)! << 4) | (v & 0x0f)]),
1401
+ []
1402
+ );
1403
+ }
1404
+
1405
+ return Buffer.concat([
1406
+ Buffer.from(data.value.block_ids.value.value),
1407
+ Buffer.from(packNibbles(data.value.block_data.value.value)),
1408
+ Buffer.from(packNibbles(data.value.sky_light.value.value)),
1409
+ Buffer.from(packNibbles(data.value.block_light.value.value)),
1410
+ Buffer.from(data.value.height_map.value.value),
1411
+ Buffer.from(data.value.grass_color.value.value),
1412
+ ]);
1413
+ },
1414
+ // TO-DO: Add a default value for this.
1415
+ },
1416
+ /**
1417
+ * A list of block entities associated with a chunk.
1418
+ *
1419
+ * @see {@link NBTSchemas.nbtSchemas.BlockEntity}
1420
+ */
1421
+ BlockEntity: {
1422
+ /**
1423
+ * The format type of the data.
1424
+ */
1425
+ type: "custom",
1426
+ /**
1427
+ * The format type that results from the {@link entryContentTypeToFormatMap.BlockEntity.parse | parse} method.
1428
+ */
1429
+ resultType: "JSONNBT",
1430
+ /**
1431
+ * The function to parse the data.
1432
+ *
1433
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
1434
+ *
1435
+ * @param data The data to parse, as a buffer.
1436
+ * @returns A promise that resolves with the parsed data.
1437
+ *
1438
+ * @throws {any} If an error occurs while parsing the data.
1439
+ */
1440
+ async parse(data: Buffer): Promise<{
1441
+ type: "compound";
1442
+ value: {
1443
+ blockEntities: {
1444
+ type: "list";
1445
+ value: { type: "compound"; value: NBTSchemas.NBTSchemaTypes.BlockEntity["value"][] };
1446
+ };
1447
+ };
1448
+ }> {
1449
+ const blockEntities: NBTSchemas.NBTSchemaTypes.BlockEntity["value"][] = [];
1450
+ let currentIndex: number = 0;
1451
+ while (currentIndex < data.length) {
1452
+ const result = await NBT.parse(data.subarray(currentIndex), "little");
1453
+ currentIndex += result.metadata.size;
1454
+ blockEntities.push(result.parsed.value as NBTSchemas.NBTSchemaTypes.BlockEntity["value"]);
1455
+ }
1456
+ return {
1457
+ type: "compound",
1458
+ value: {
1459
+ blockEntities: {
1460
+ type: "list",
1461
+ value: {
1462
+ type: "compound",
1463
+ value: blockEntities,
1464
+ },
1465
+ },
1466
+ },
1467
+ };
1468
+ },
1469
+ /**
1470
+ * The function to serialize the data.
1471
+ *
1472
+ * This result of this can be written directly to the file or LevelDB entry.
1473
+ *
1474
+ * @param data The data to serialize.
1475
+ * @returns The serialized data, as a buffer.
1476
+ */
1477
+ serialize(data: {
1478
+ type: "compound";
1479
+ value: {
1480
+ blockEntities: {
1481
+ type: "list";
1482
+ value: { type: "compound"; value: NBTSchemas.NBTSchemaTypes.BlockEntity["value"][] };
1483
+ };
1484
+ };
1485
+ }): Buffer<ArrayBuffer> {
1486
+ const nbtData: Buffer[] = data.value.blockEntities.value.value.map(
1487
+ (blockEntity: NBTSchemas.NBTSchemaTypes.BlockEntity["value"]): Buffer =>
1488
+ NBT.writeUncompressed({ name: "", type: "compound", value: blockEntity }, "little")
1489
+ );
1490
+ return Buffer.concat(nbtData);
1491
+ },
1492
+ },
1493
+ /**
1494
+ * A list of entities associated with a chunk.
1495
+ *
1496
+ * @deprecated No longer used.
1497
+ *
1498
+ * @todo Figure out what version this was deprecated in (it exists in v1.16.40 but not in 1.21.51).
1499
+ */
1500
+ Entity: {
1501
+ /**
1502
+ * The format type of the data.
1503
+ */
1504
+ type: "custom",
1505
+ /**
1506
+ * The format type that results from the {@link entryContentTypeToFormatMap.Entity.parse | parse} method.
1507
+ */
1508
+ resultType: "JSONNBT",
1509
+ /**
1510
+ * The function to parse the data.
1511
+ *
1512
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
1513
+ *
1514
+ * @param data The data to parse, as a buffer.
1515
+ * @returns A promise that resolves with the parsed data.
1516
+ *
1517
+ * @throws {any} If an error occurs while parsing the data.
1518
+ */
1519
+ async parse(data: Buffer): Promise<{
1520
+ type: "compound";
1521
+ value: {
1522
+ entities: {
1523
+ type: "list";
1524
+ value: { type: "compound"; value: NBTSchemas.NBTSchemaTypes.ActorPrefix["value"][] };
1525
+ };
1526
+ };
1527
+ }> {
1528
+ const entities: NBTSchemas.NBTSchemaTypes.ActorPrefix["value"][] = [];
1529
+ let currentIndex: number = 0;
1530
+ while (currentIndex < data.length) {
1531
+ const result = await NBT.parse(data.subarray(currentIndex), "little");
1532
+ currentIndex += result.metadata.size;
1533
+ entities.push(result.parsed.value as NBTSchemas.NBTSchemaTypes.ActorPrefix["value"]);
1534
+ }
1535
+ return {
1536
+ type: "compound",
1537
+ value: {
1538
+ entities: {
1539
+ type: "list",
1540
+ value: {
1541
+ type: "compound",
1542
+ value: entities,
1543
+ },
1544
+ },
1545
+ },
1546
+ };
1547
+ },
1548
+ /**
1549
+ * The function to serialize the data.
1550
+ *
1551
+ * This result of this can be written directly to the file or LevelDB entry.
1552
+ *
1553
+ * @param data The data to serialize.
1554
+ * @returns The serialized data, as a buffer.
1555
+ */
1556
+ serialize(data: {
1557
+ type: "compound";
1558
+ value: {
1559
+ entities: {
1560
+ type: "list";
1561
+ value: { type: "compound"; value: NBTSchemas.NBTSchemaTypes.ActorPrefix["value"][] };
1562
+ };
1563
+ };
1564
+ }): Buffer<ArrayBuffer> {
1565
+ const nbtData: Buffer[] = data.value.entities.value.value.map(
1566
+ (entity: NBTSchemas.NBTSchemaTypes.ActorPrefix["value"]): Buffer =>
1567
+ NBT.writeUncompressed({ name: "", type: "compound", value: entity }, "little")
1568
+ );
1569
+ return Buffer.concat(nbtData);
1570
+ },
1571
+ },
1572
+ /**
1573
+ * @see {@link NBTSchemas.nbtSchemas.PendingTicks}
1574
+ *
1575
+ * @todo Add a description for this.
1576
+ */
1577
+ PendingTicks: {
1578
+ /**
1579
+ * The format type of the data.
1580
+ */
1581
+ type: "NBT",
1582
+ /**
1583
+ * The default value to use when initializing a new entry.
1584
+ *
1585
+ * @todo Add a link to the object with the default value once it is made.
1586
+ */
1587
+ get defaultValue(): Buffer<ArrayBuffer> {
1588
+ const result: Buffer<ArrayBuffer> = Buffer.from(
1589
+ NBT.writeUncompressed(
1590
+ {
1591
+ name: "",
1592
+ type: "compound",
1593
+ value: {
1594
+ currentTick: {
1595
+ type: "int",
1596
+ value: 0,
1597
+ },
1598
+ tickList: {
1599
+ type: "list",
1600
+ value: {
1601
+ type: "compound",
1602
+ value: [
1603
+ {
1604
+ tileID: {
1605
+ type: "int",
1606
+ value: 0,
1607
+ },
1608
+ blockState: {
1609
+ type: "compound",
1610
+ value: {
1611
+ name: {
1612
+ type: "string",
1613
+ value: "minecraft:air",
1614
+ },
1615
+ states: {
1616
+ type: "compound",
1617
+ value: {},
1618
+ },
1619
+ version: {
1620
+ type: "int",
1621
+ value: 0,
1622
+ },
1623
+ },
1624
+ },
1625
+ time: {
1626
+ type: "long",
1627
+ value: toLongParts(0n),
1628
+ },
1629
+ x: {
1630
+ type: "int",
1631
+ value: 0,
1632
+ },
1633
+ y: {
1634
+ type: "int",
1635
+ value: 0,
1636
+ },
1637
+ z: {
1638
+ type: "int",
1639
+ value: 0,
1640
+ },
1641
+ },
1642
+ ],
1643
+ },
1644
+ },
1645
+ },
1646
+ } satisfies NBTSchemas.NBTSchemaTypes.PendingTicks & NBT.NBT,
1647
+ "little"
1648
+ )
1649
+ );
1650
+ Object.defineProperty(this, "defaultValue", { value: result, configurable: true, enumerable: true, writable: false });
1651
+ return result;
1652
+ },
1653
+ },
1654
+ /**
1655
+ * @deprecated Only used in versions < 1.2.3.
1656
+ *
1657
+ * @todo Figure out how to parse this.
1658
+ * @todo Add a description for this.
1659
+ *
1660
+ * @see https://github.com/yechentide/core-bedrock/blob/77d815439da14b3d0b7d0335b80deb1860aee42a/Analysis/chunk-0x34-legacy-block-extra-data.md
1661
+ * @see https://github.com/robofinch/Prismarine-Anchor/blob/main/crates/bedrock/leveldb-entries/src/entries/legacy_extra_block_data.rs
1662
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L331
1663
+ */
1664
+ LegacyBlockExtraData: {
1665
+ /**
1666
+ * The format type of the data.
1667
+ */
1668
+ type: "unknown",
1669
+ },
1670
+ /**
1671
+ * @todo Figure out what this is used for.
1672
+ * @todo Add a description for this.
1673
+ *
1674
+ * @see {@link NBTSchemas.nbtSchemas.BiomeState}
1675
+ *
1676
+ * @description
1677
+ * https://github.com/robofinch/Prismarine-Anchor/blob/6ce90fa2a27c5d81e6cc1cc376e91c757a80e321/crates/bedrock/leveldb-entries/src/entries/biome_state.rs#L103C1-L109C41
1678
+ *
1679
+ * The above source says the following:
1680
+ *
1681
+ * > These state values seem to have something to do with snow accumulation level.
1682
+ * Maybe the maximum level that snow can accumulate to naturally.
1683
+ *
1684
+ * > TODO: determine this
1685
+ *
1686
+ * > Also, note that these could be backed by HashMaps,
1687
+ *
1688
+ * > but the order of real game data is inconsistent, and have very, very few values.
1689
+ *
1690
+ * > This makes things easier for testing to be able to round-trip,
1691
+ * and is probably more performant, too.
1692
+ */
1693
+ BiomeState: {
1694
+ // https://github.com/yechentide/core-bedrock/blob/77d815439da14b3d0b7d0335b80deb1860aee42a/Analysis/chunk-0x35-biome-state.md
1695
+ //
1696
+ // https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L344C5-L344C15
1697
+ /**
1698
+ * The format type of the data.
1699
+ */
1700
+ type: "custom",
1701
+ /**
1702
+ * The format type that results from the {@link entryContentTypeToFormatMap.BiomeState.parse | parse} method.
1703
+ */
1704
+ resultType: "JSONNBT",
1705
+ // TODO: Figure out what each of the state values mean.
1706
+ /**
1707
+ * The function to parse the data.
1708
+ *
1709
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
1710
+ *
1711
+ * @param data The data to parse, as a buffer.
1712
+ * @returns The parsed data.
1713
+ *
1714
+ * @throws {RangeError} If the buffer is empty.
1715
+ * @throws {RangeError} If the number of BiomeState entries does is less than the number indicated in buffer.
1716
+ * @throws {RangeError} If one of the BiomeState entries does not contain enough bytes.
1717
+ * @throws {TypeError} If the BiomeState format is unknown.
1718
+ */
1719
+ // OPTIMIZE: This parse method can be optimized.
1720
+ parse(data: Buffer): NBTSchemas.NBTSchemaTypes.BiomeState {
1721
+ if (data.length < 1) throw new RangeError("BiomeState Buffer must be at least 1 byte long");
1722
+ let biomeStateFormat: 1 | 2;
1723
+ let count: number;
1724
+ formatDetection: {
1725
+ count = data[0]!;
1726
+ if (data.length === 1 + count * 2) {
1727
+ biomeStateFormat = 1;
1728
+ break formatDetection;
1729
+ }
1730
+ if (data.length < 2) throw new TypeError("Unknown BiomeState format");
1731
+ count = data[0]! | (data[1]! << 8);
1732
+ if (data.length === 2 + count * 3) {
1733
+ biomeStateFormat = 2;
1734
+ break formatDetection;
1735
+ }
1736
+ throw new TypeError("Unknown BiomeState format");
1737
+ }
1738
+ switch (biomeStateFormat) {
1739
+ case 1: {
1740
+ const entries: { biome_id: number; state: number }[] = [];
1741
+ for (let i = 0; i < count; i++) {
1742
+ const entryData: Buffer = data.subarray(1 + i * 2, 3 + i * 2);
1743
+ if (entryData.length !== 2) throw new RangeError("BiomeState entry data must be 2 bytes long");
1744
+ const [biome_id, state] = entryData as Buffer & [number, number];
1745
+ entries.push({ biome_id, state });
1746
+ }
1747
+ return {
1748
+ type: "compound",
1749
+ value: {
1750
+ format: {
1751
+ type: "byte",
1752
+ value: biomeStateFormat,
1753
+ },
1754
+ entries: {
1755
+ type: "list",
1756
+ value: {
1757
+ type: "compound",
1758
+ value: entries.map((entry) => ({
1759
+ biome_id: {
1760
+ type: "byte",
1761
+ value: entry.biome_id as Extract<
1762
+ NBTSchemas.NBTSchemaTypes.BiomeState["value"],
1763
+ { format: { value: 1 } }
1764
+ >["entries"]["value"]["value"][number]["biome_id"]["value"],
1765
+ },
1766
+ state: { type: "byte", value: entry.state },
1767
+ })),
1768
+ },
1769
+ },
1770
+ },
1771
+ };
1772
+ }
1773
+ case 2: {
1774
+ const entries: { biome_id: number; state: number }[] = [];
1775
+ for (let i = 0; i < count; i++) {
1776
+ const entryData: Buffer = data.subarray(2 + i * 3, 5 + i * 3);
1777
+ if (entryData.length !== 3) throw new RangeError("BiomeState entry data must be 3 bytes long");
1778
+ const biome_id: number = entryData.readUInt16LE(0);
1779
+ const state: number = entryData[2]!;
1780
+ entries.push({ biome_id, state });
1781
+ }
1782
+ return {
1783
+ type: "compound",
1784
+ value: {
1785
+ format: {
1786
+ type: "byte",
1787
+ value: biomeStateFormat,
1788
+ },
1789
+ entries: {
1790
+ type: "list",
1791
+ value: {
1792
+ type: "compound",
1793
+ value: entries.map(
1794
+ (entry: {
1795
+ biome_id: number;
1796
+ state: number;
1797
+ }): { biome_id: { type: "short"; value: number }; state: { type: "byte"; value: number } } => ({
1798
+ biome_id: { type: "short", value: entry.biome_id },
1799
+ state: { type: "byte", value: entry.state },
1800
+ })
1801
+ ),
1802
+ },
1803
+ },
1804
+ },
1805
+ };
1806
+ }
1807
+ }
1808
+ },
1809
+ /**
1810
+ * The function to serialize the data.
1811
+ *
1812
+ * This result of this can be written directly to the file or LevelDB entry.
1813
+ *
1814
+ * @param data The data to serialize.
1815
+ * @returns The serialized data, as a buffer.
1816
+ *
1817
+ * @throws {TypeError} If one of the BiomeState entries are undefined.
1818
+ * @throws {TypeError} If the BiomeState format is unknown.
1819
+ */
1820
+ serialize(data: NBTSchemas.NBTSchemaTypes.BiomeState): Buffer<ArrayBuffer> {
1821
+ const entries: typeof data.value.entries.value.value = data.value.entries.value.value;
1822
+ const count: number = entries.length;
1823
+ switch (data.value.format.value) {
1824
+ case 1: {
1825
+ const serializedData: Buffer<ArrayBuffer> = Buffer.alloc(1 + count * 2);
1826
+ serializedData.writeUInt8(count, 0);
1827
+ for (let i = 0; i < count; i++) {
1828
+ const entry: (typeof entries)[number] | undefined = entries[i];
1829
+ if (!entry) throw new TypeError(`BiomeState entry is undefined at index ${i}`);
1830
+ const offset: number = 1 + i * 2;
1831
+ serializedData[offset] = entry.biome_id.value;
1832
+ serializedData[offset + 1] = entry.state.value;
1833
+ }
1834
+ return serializedData;
1835
+ }
1836
+ case 2: {
1837
+ const serializedData: Buffer<ArrayBuffer> = Buffer.alloc(2 + count * 3);
1838
+ serializedData.writeUInt16LE(count, 0);
1839
+ for (let i = 0; i < count; i++) {
1840
+ const entry: (typeof data.value.entries.value.value)[number] | undefined = entries[i];
1841
+ if (!entry) throw new TypeError(`BiomeState entry is undefined at index ${i}`);
1842
+ const offset: number = 2 + i * 3;
1843
+ serializedData[offset] = entry.biome_id.value & 0xff;
1844
+ serializedData[offset + 1] = entry.biome_id.value >>> 8;
1845
+ serializedData[offset + 2] = entry.state.value;
1846
+ }
1847
+ return serializedData;
1848
+ }
1849
+ default:
1850
+ throw new TypeError(`Unknown BiomeState format: ${(data.value.format as { type: "byte"; value: number }).value}`);
1851
+ }
1852
+ },
1853
+ },
1854
+ /**
1855
+ * A value that indicates the finalization state of a chunk's data.
1856
+ *
1857
+ * Possible values:
1858
+ * - `0`: NeedsInstaticking - Chunk needs to be ticked
1859
+ * - `1`: NeedsPopulation - Chunk needs to be populated with mobs
1860
+ * - `2`: Done - Chunk generation is fully complete
1861
+ */
1862
+ FinalizedState: {
1863
+ // https://github.com/reedacartwright/rbedrock/blob/c9040e803c4172a19507bc8f1278802ad0e6170a/R/finalized_state.R#L4
1864
+ /**
1865
+ * The format type of the data.
1866
+ */
1867
+ type: "int",
1868
+ /**
1869
+ * How many bytes this integer is.
1870
+ */
1871
+ bytes: 4,
1872
+ /**
1873
+ * The endianness of the data.
1874
+ */
1875
+ format: "LE",
1876
+ /**
1877
+ * The signedness of the data.
1878
+ */
1879
+ signed: false,
1880
+ /**
1881
+ * The default value to use when initializing a new entry.
1882
+ *
1883
+ * Bytes:
1884
+ * ```json
1885
+ * [0,0,0,0]
1886
+ * ```
1887
+ */
1888
+ defaultValue: Buffer.from([0, 0, 0, 0]),
1889
+ },
1890
+ /**
1891
+ * @deprecated No longer used.
1892
+ *
1893
+ * @todo Figure out how to parse this.
1894
+ * @todo Add a description for this.
1895
+ *
1896
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L382
1897
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L1445
1898
+ */
1899
+ ConversionData: {
1900
+ /**
1901
+ * The format type of the data.
1902
+ */
1903
+ type: "unknown",
1904
+ },
1905
+ /**
1906
+ * The border block data for the chunk.
1907
+ *
1908
+ * @todo Add a better description for this.
1909
+ */
1910
+ BorderBlocks: {
1911
+ // https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext?plain=1#L397
1912
+ // https://github.com/robofinch/Prismarine-Anchor/blob/main/crates/bedrock/leveldb-entries/src/entries/border_blocks.rs
1913
+ /**
1914
+ * The format type of the data.
1915
+ */
1916
+ type: "custom",
1917
+ /**
1918
+ * The format type that results from the {@link entryContentTypeToFormatMap.BorderBlocks.parse | parse} method.
1919
+ */
1920
+ resultType: "JSONNBT",
1921
+ /**
1922
+ * The function to parse the data.
1923
+ *
1924
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
1925
+ *
1926
+ * @param data The data to parse, as a buffer.
1927
+ * @returns The parsed data.
1928
+ *
1929
+ * @throws {RangeError} If the buffer is empty.
1930
+ * @throws {RangeError} If the length of the buffer is not equal to the expected length based on the first byte.
1931
+ */
1932
+ parse(data: Buffer): NBTSchemas.NBTSchemaTypes.BorderBlocks {
1933
+ if (data.length < 1) throw new RangeError("BorderBlocks buffer must be at least 1 byte long");
1934
+
1935
+ const count: number = data[0]!;
1936
+
1937
+ if (data.length !== 1 + count) throw new RangeError(`BorderBlocks length mismatch, expected ${1 + count}, got ${data.length}`);
1938
+
1939
+ const blocks: {
1940
+ x: { type: "byte"; value: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 };
1941
+ z: { type: "byte"; value: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 };
1942
+ }[] = new Array(count);
1943
+
1944
+ for (let i = 0; i < count; i++) {
1945
+ const encoded: number = data[1 + i]!;
1946
+ const x: number = encoded >>> 4;
1947
+ const z: number = encoded & 0x0f;
1948
+
1949
+ blocks[i] = {
1950
+ x: { type: "byte", value: x as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 },
1951
+ z: { type: "byte", value: z as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 },
1952
+ };
1953
+ }
1954
+
1955
+ return {
1956
+ type: "compound",
1957
+ value: {
1958
+ BorderBlocks: {
1959
+ type: "list",
1960
+ value: {
1961
+ type: "compound",
1962
+ value: blocks,
1963
+ },
1964
+ },
1965
+ },
1966
+ };
1967
+ },
1968
+ /**
1969
+ * The function to serialize the data.
1970
+ *
1971
+ * This result of this can be written directly to the file or LevelDB entry.
1972
+ *
1973
+ * @param data The data to serialize.
1974
+ * @returns The serialized data, as a buffer.
1975
+ *
1976
+ * @throws {TypeError} If one of the BorderBlocks entries are undefined.
1977
+ */
1978
+ serialize(data: NBTSchemas.NBTSchemaTypes.BorderBlocks): Buffer<ArrayBuffer> {
1979
+ const blocks: typeof data.value.BorderBlocks.value.value = data.value.BorderBlocks.value.value;
1980
+ const count: number = blocks.length;
1981
+
1982
+ const serializedData: Buffer<ArrayBuffer> = Buffer.alloc(1 + count);
1983
+
1984
+ serializedData[0] = count;
1985
+
1986
+ for (let i = 0; i < count; i++) {
1987
+ const entry: (typeof blocks)[number] | undefined = blocks[i];
1988
+ if (!entry) throw new TypeError(`BorderBlocks entry is undefined at index ${i}`);
1989
+
1990
+ const x: number = entry.x.value;
1991
+ const z: number = entry.z.value;
1992
+
1993
+ serializedData[1 + i] = (x << 4) | (z & 0x0f);
1994
+ }
1995
+
1996
+ return serializedData;
1997
+ },
1998
+ },
1999
+ /**
2000
+ * Bounding boxes for structure spawns, such as a Nether Fortress or Pillager Outpost.
2001
+ *
2002
+ * @deprecated Replaced with {@link entryContentTypeToFormatMap.AABBVolumes | AABBVolumes} in either 1.21.10 or one of the 1.21.10 previews.
2003
+ */
2004
+ HardcodedSpawners: {
2005
+ // https://github.com/reedacartwright/rbedrock/blob/c9040e803c4172a19507bc8f1278802ad0e6170a/man/HardcodedSpawnArea.Rd#L4
2006
+ // https://github.com/reedacartwright/rbedrock/blob/c9040e803c4172a19507bc8f1278802ad0e6170a/R/legacy-hsa.R#L44
2007
+ // https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L405C5-L405C22
2008
+ //
2009
+ // https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L1481
2010
+ // One of the sources seem to indicate this was removed in 1.21.10.
2011
+ /**
2012
+ * The format type of the data.
2013
+ */
2014
+ type: "custom",
2015
+ /**
2016
+ * The format type that results from the {@link entryContentTypeToFormatMap.HardcodedSpawners.parse | parse} method.
2017
+ */
2018
+ resultType: "JSONNBT",
2019
+ /**
2020
+ * The function to parse the data.
2021
+ *
2022
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
2023
+ *
2024
+ * @param data The data to parse, as a buffer.
2025
+ * @returns The parsed data.
2026
+ *
2027
+ * @throws {RangeError} If the buffer is less than 4 bytes.
2028
+ * @throws {RangeError} If the length of the buffer is not equal to the expected length based on the first four bytes.
2029
+ */
2030
+ parse(data: Buffer): NBTSchemas.NBTSchemaTypes.HardcodedSpawners {
2031
+ if (data.length < 4) throw new RangeError("HardcodedSpawners buffer must be at least 4 bytes long");
2032
+
2033
+ const count: number = data.readUInt32LE(0);
2034
+
2035
+ if (data.length !== 4 + count * 25) throw new RangeError(`HardcodedSpawners length mismatch, expected ${4 + count * 25}, got ${data.length}`);
2036
+
2037
+ const entries: NBTSchemas.NBTSchemaTypes.HardcodedSpawners["value"]["HardcodedSpawningAreas"]["value"]["value"] = new Array(count);
2038
+
2039
+ let offset = 4;
2040
+ for (let i = 0; i < count; i++, offset += 25) {
2041
+ entries[i] = {
2042
+ AABBMinX: { type: "int", value: data.readInt32LE(offset) },
2043
+ AABBMinY: { type: "int", value: data.readInt32LE(offset + 4) },
2044
+ AABBMinZ: { type: "int", value: data.readInt32LE(offset + 8) },
2045
+ AABBMaxX: { type: "int", value: data.readInt32LE(offset + 12) },
2046
+ AABBMaxY: { type: "int", value: data.readInt32LE(offset + 16) },
2047
+ AABBMaxZ: { type: "int", value: data.readInt32LE(offset + 20) },
2048
+ Type: { type: "byte", value: data[offset + 24]! as (typeof entries)[number]["Type"]["value"] },
2049
+ };
2050
+ }
2051
+
2052
+ return {
2053
+ type: "compound",
2054
+ value: {
2055
+ HardcodedSpawningAreas: {
2056
+ type: "list",
2057
+ value: {
2058
+ type: "compound",
2059
+ value: entries,
2060
+ },
2061
+ },
2062
+ },
2063
+ };
2064
+ },
2065
+ /**
2066
+ * The function to serialize the data.
2067
+ *
2068
+ * This result of this can be written directly to the file or LevelDB entry.
2069
+ *
2070
+ * @param data The data to serialize.
2071
+ * @returns The serialized data, as a buffer.
2072
+ *
2073
+ * @throws {TypeError} If one of the HardcodedSpawners entries are undefined.
2074
+ */
2075
+ serialize(data: NBTSchemas.NBTSchemaTypes.HardcodedSpawners): Buffer<ArrayBuffer> {
2076
+ const count: number = data.value.HardcodedSpawningAreas.value.value.length;
2077
+ const serializedData: Buffer<ArrayBuffer> = Buffer.alloc(4 + count * 25);
2078
+
2079
+ serializedData.writeUInt32LE(count, 0);
2080
+
2081
+ const entries: NBTSchemas.NBTSchemaTypes.HardcodedSpawners["value"]["HardcodedSpawningAreas"]["value"]["value"] =
2082
+ data.value.HardcodedSpawningAreas.value.value;
2083
+
2084
+ let offset = 4;
2085
+ for (let i = 0; i < count; i++, offset += 25) {
2086
+ const entry: (typeof entries)[number] | undefined = entries[i];
2087
+ if (!entry) throw new TypeError(`HardcodedSpawners entry is undefined at index ${i}`);
2088
+
2089
+ serializedData.writeInt32LE(entry.AABBMinX.value, offset);
2090
+ serializedData.writeInt32LE(entry.AABBMinY.value, offset + 4);
2091
+ serializedData.writeInt32LE(entry.AABBMinZ.value, offset + 8);
2092
+ serializedData.writeInt32LE(entry.AABBMaxX.value, offset + 12);
2093
+ serializedData.writeInt32LE(entry.AABBMaxY.value, offset + 16);
2094
+ serializedData.writeInt32LE(entry.AABBMaxZ.value, offset + 20);
2095
+ serializedData[offset + 24] = entry.Type.value;
2096
+ }
2097
+
2098
+ return serializedData;
2099
+ },
2100
+ },
2101
+ /**
2102
+ * @see {@link NBTSchemas.nbtSchemas.RandomTicks}
2103
+ *
2104
+ * @todo Add a description for this.
2105
+ */
2106
+ RandomTicks: {
2107
+ /**
2108
+ * The format type of the data.
2109
+ */
2110
+ type: "NBT",
2111
+ /**
2112
+ * The default value to use when initializing a new entry.
2113
+ *
2114
+ * @todo Add a link to the object with the default value once it is made.
2115
+ */
2116
+ get defaultValue(): Buffer<ArrayBuffer> {
2117
+ const result: Buffer<ArrayBuffer> = Buffer.from(
2118
+ NBT.writeUncompressed(
2119
+ {
2120
+ name: "",
2121
+ type: "compound",
2122
+ value: {
2123
+ currentTick: {
2124
+ type: "int",
2125
+ value: 0,
2126
+ },
2127
+ tickList: {
2128
+ type: "list",
2129
+ value: {
2130
+ type: "compound",
2131
+ value: [
2132
+ {
2133
+ blockState: {
2134
+ type: "compound",
2135
+ value: {
2136
+ name: {
2137
+ type: "string",
2138
+ value: "minecraft:air",
2139
+ },
2140
+ states: {
2141
+ type: "compound",
2142
+ value: {},
2143
+ },
2144
+ version: {
2145
+ type: "int",
2146
+ value: 0,
2147
+ },
2148
+ },
2149
+ },
2150
+ time: {
2151
+ type: "long",
2152
+ value: toLongParts(0n),
2153
+ },
2154
+ x: {
2155
+ type: "int",
2156
+ value: 0,
2157
+ },
2158
+ y: {
2159
+ type: "int",
2160
+ value: 0,
2161
+ },
2162
+ z: {
2163
+ type: "int",
2164
+ value: 0,
2165
+ },
2166
+ },
2167
+ ],
2168
+ },
2169
+ },
2170
+ },
2171
+ } satisfies NBTSchemas.NBTSchemaTypes.RandomTicks & NBT.NBT,
2172
+ "little"
2173
+ )
2174
+ );
2175
+ Object.defineProperty(this, "defaultValue", { value: result, configurable: true, enumerable: true, writable: false });
2176
+ return result;
2177
+ },
2178
+ },
2179
+ /**
2180
+ * The low segment of the 4 byte halves of the seed value used to generate the chunk.
2181
+ *
2182
+ * The low segment can also be found by reading the first 4 bytes of the seed as a little-endian signed integer when it is encoded as a little-endian 64-bit signed integer.
2183
+ *
2184
+ * @todo Check if this still exists (I have only seen it in a world from a 1.19.60.22 dev build).
2185
+ */
2186
+ GenerationSeed: {
2187
+ /**
2188
+ * The format type of the data.
2189
+ */
2190
+ type: "int",
2191
+ /**
2192
+ * How many bytes this integer is.
2193
+ */
2194
+ bytes: 4,
2195
+ /**
2196
+ * The endianness of the data.
2197
+ */
2198
+ format: "LE",
2199
+ /**
2200
+ * The signedness of the data.
2201
+ */
2202
+ signed: true,
2203
+ },
2204
+ /**
2205
+ * xxHash64 checksums of `SubChunkPrefix`, `BlockEntity`, `Entity`, and `Data2D` values.
2206
+ *
2207
+ * @deprecated Only used in versions < 1.18.0.
2208
+ *
2209
+ * @todo Figure out how to parse this.
2210
+ */
2211
+ Checksums: {
2212
+ /**
2213
+ * The format type of the data.
2214
+ */
2215
+ type: "unknown",
2216
+ },
2217
+ /**
2218
+ * UNDOCUMENTED.
2219
+ *
2220
+ * Possible values:
2221
+ * - `0`: False
2222
+ * - `1`: True
2223
+ *
2224
+ * @todo Try to get a world with this to see how it works.
2225
+ * @todo Add a description for this.
2226
+ *
2227
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext?plain=1#L481
2228
+ *
2229
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L1470
2230
+ * One of the sources seem to indicate this was added in 1.17.30.25. (Verify this, then add the \@since tag.)
2231
+ */
2232
+ GeneratedPreCavesAndCliffsBlending: {
2233
+ /**
2234
+ * The format type of the data.
2235
+ */
2236
+ type: "int",
2237
+ /**
2238
+ * How many bytes this integer is.
2239
+ */
2240
+ bytes: 1,
2241
+ /**
2242
+ * The endianness of the data.
2243
+ */
2244
+ format: "LE",
2245
+ /**
2246
+ * The signedness of the data.
2247
+ */
2248
+ signed: false,
2249
+ /**
2250
+ * The default value to use when initializing a new entry.
2251
+ *
2252
+ * Bytes:
2253
+ * ```json
2254
+ * [0]
2255
+ * ```
2256
+ */
2257
+ defaultValue: Buffer.from([0]),
2258
+ },
2259
+ /**
2260
+ * @todo Figure out how to parse this.
2261
+ * @todo Add a description for this.
2262
+ *
2263
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L1471
2264
+ * One of the sources seem to indicate this was added in 1.17.30.25.
2265
+ */
2266
+ BlendingBiomeHeight: {
2267
+ /**
2268
+ * The format type of the data.
2269
+ */
2270
+ type: "unknown",
2271
+ },
2272
+ /**
2273
+ * A hash which is the key in the `LevelChunkMetaDataDictionary` record
2274
+ * for the NBT metadata of this chunk. Seems like it might default to something dependent
2275
+ * on the current game or chunk version.
2276
+ *
2277
+ * This is an unsigned 64-bit hex value (16 digits, 8 bytes).
2278
+ */
2279
+ MetaDataHash: {
2280
+ /**
2281
+ * The format type of the data.
2282
+ */
2283
+ type: "hex",
2284
+ /**
2285
+ * The default value to use when initializing a new entry.
2286
+ *
2287
+ * Bytes:
2288
+ * ```json
2289
+ * [0, 0, 0, 0, 0, 0, 0, 0]
2290
+ * ```
2291
+ */
2292
+ defaultValue: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]),
2293
+ /**
2294
+ * The minimum length of the data in bytes (if the hex data is a string of hexadecimal characters, two characters is one byte).
2295
+ */
2296
+ minLength: 8,
2297
+ /**
2298
+ * The maximum length of the data in bytes (if the hex data is a string of hexadecimal characters, two characters is one byte).
2299
+ */
2300
+ maxLength: 8,
2301
+ },
2302
+ /**
2303
+ * @todo Figure out how to parse this.
2304
+ * @todo Add a description for this.
2305
+ *
2306
+ * @see https://github.com/robofinch/Prismarine-Anchor/blob/main/crates/bedrock/leveldb-entries/src/entries/blending_data.rs#L10
2307
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L526
2308
+ *
2309
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L1475
2310
+ * One of the sources seem to indicate this was added in 1.17.30.
2311
+ */
2312
+ BlendingData: {
2313
+ /**
2314
+ * The format type of the data.
2315
+ */
2316
+ type: "unknown",
2317
+ },
2318
+ /**
2319
+ * The version of the actor digest data.
2320
+ *
2321
+ * Current known versions:
2322
+ * - `0`: 1.18.30 Format
2323
+ */
2324
+ ActorDigestVersion: {
2325
+ // https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L526
2326
+ /**
2327
+ * The format type of the data.
2328
+ */
2329
+ type: "int",
2330
+ /**
2331
+ * How many bytes this integer is.
2332
+ */
2333
+ bytes: 1,
2334
+ /**
2335
+ * The endianness of the data.
2336
+ */
2337
+ format: "LE",
2338
+ /**
2339
+ * The signedness of the data.
2340
+ */
2341
+ signed: false,
2342
+ /**
2343
+ * The default value to use when initializing a new entry.
2344
+ *
2345
+ * Bytes:
2346
+ * ```json
2347
+ * [0]
2348
+ * ```
2349
+ */
2350
+ defaultValue: Buffer.from([0]),
2351
+ },
2352
+ /**
2353
+ * The version of a chunk for versions < 1.16.100.
2354
+ *
2355
+ * Possible values:
2356
+ * - `0`: v0.9.0
2357
+ * - `1`: v0.9.2
2358
+ * - `2`: v0.9.5
2359
+ * - `3`: v0.17.0.1
2360
+ * - `4`: v1.1.0
2361
+ * - `5`: Converted from console to v1.1.0
2362
+ * - `6`: v1.2.0.2
2363
+ * - `7`: v1.2.0
2364
+ * - `8`: v1.2.13
2365
+ * - `9`: v1.8.0
2366
+ * - `10`: v1.9.0
2367
+ * - `11`: v1.11.0.1
2368
+ * - `12`: v1.11.0.3
2369
+ * - `13`: v1.11.0.4
2370
+ * - `14`: v1.11.1
2371
+ * - `15`: v1.12.0.4
2372
+ * - `16`: v1.14.0
2373
+ * - `17`: v1.15.0
2374
+ * - `18`: v1.16.0.51
2375
+ * - `19`: v1.16.0
2376
+ *
2377
+ * @deprecated Only used in versions < 1.16.100. Later versions use {@link entryContentTypeToFormatMap.Version | Version}
2378
+ */
2379
+ LegacyVersion: {
2380
+ // https://github.com/reedacartwright/rbedrock/blob/c9040e803c4172a19507bc8f1278802ad0e6170a/R/legacy-chunk_version.R
2381
+ // https://github.com/robofinch/Prismarine-Anchor/blob/6ce90fa2a27c5d81e6cc1cc376e91c757a80e321/crates/bedrock/bedrock-entries/src/enum_types/chunk_version.rs
2382
+ // https://github.com/reedacartwright/rbedrock/blob/c9040e803c4172a19507bc8f1278802ad0e6170a/man/LegacyChunkVersion.Rd
2383
+ // Value list: https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L98
2384
+ // https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L602
2385
+ /**
2386
+ * The format type of the data.
2387
+ */
2388
+ type: "int",
2389
+ /**
2390
+ * How many bytes this integer is.
2391
+ */
2392
+ bytes: 1,
2393
+ /**
2394
+ * The endianness of the data.
2395
+ */
2396
+ format: "LE",
2397
+ /**
2398
+ * The signedness of the data.
2399
+ */
2400
+ signed: false,
2401
+ },
2402
+ /**
2403
+ * @deprecated It is unknown when this was removed, it was found in a Windows 10 Edition Beta v0.15.0 world.
2404
+ *
2405
+ * @todo Add a schema for this.
2406
+ * @todo Add a description for this.
2407
+ */
2408
+ MVillages: {
2409
+ /**
2410
+ * The format type of the data.
2411
+ */
2412
+ type: "NBT",
2413
+ },
2414
+ /**
2415
+ * @deprecated It is unknown when this was removed.
2416
+ *
2417
+ * @todo Add a schema for this.
2418
+ * @todo Add a description for this.
2419
+ */
2420
+ Villages: {
2421
+ /**
2422
+ * The format type of the data.
2423
+ */
2424
+ type: "NBT",
2425
+ },
2426
+ /**
2427
+ * @see {@link NBTSchemas.nbtSchemas.VillageDwellers}
2428
+ *
2429
+ * @todo Add a description for this.
2430
+ */
2431
+ VillageDwellers: {
2432
+ /**
2433
+ * The format type of the data.
2434
+ */
2435
+ type: "NBT",
2436
+ },
2437
+ /**
2438
+ * @see {@link NBTSchemas.nbtSchemas.VillageInfo}
2439
+ *
2440
+ * @todo Add a description for this.
2441
+ */
2442
+ VillageInfo: {
2443
+ /**
2444
+ * The format type of the data.
2445
+ */
2446
+ type: "NBT",
2447
+ },
2448
+ /**
2449
+ * @see {@link NBTSchemas.nbtSchemas.VillagePOI}
2450
+ *
2451
+ * @todo Add a description for this.
2452
+ */
2453
+ VillagePOI: {
2454
+ /**
2455
+ * The format type of the data.
2456
+ */
2457
+ type: "NBT",
2458
+ },
2459
+ /**
2460
+ * @see {@link NBTSchemas.nbtSchemas.VillagePlayers}
2461
+ *
2462
+ * @todo Add a description for this.
2463
+ */
2464
+ VillagePlayers: {
2465
+ /**
2466
+ * The format type of the data.
2467
+ */
2468
+ type: "NBT",
2469
+ },
2470
+ /**
2471
+ * @see {@link NBTSchemas.nbtSchemas.VillageRaid}
2472
+ *
2473
+ * @todo Add a description for this.
2474
+ */
2475
+ VillageRaid: {
2476
+ /**
2477
+ * The format type of the data.
2478
+ */
2479
+ type: "NBT",
2480
+ },
2481
+ /**
2482
+ * A player's data.
2483
+ *
2484
+ * @see {@link NBTSchemas.nbtSchemas.Player}
2485
+ */
2486
+ Player: {
2487
+ /**
2488
+ * The format type of the data.
2489
+ */
2490
+ type: "NBT",
2491
+ },
2492
+ /**
2493
+ * A player's client data.
2494
+ *
2495
+ * This includes the key for the player's {@link entryContentTypeToFormatMap.Player | server data}, the player's Microsoft account ID, and the player's self-signed ID.
2496
+ *
2497
+ * @see {@link NBTSchemas.nbtSchemas.PlayerClient}
2498
+ */
2499
+ PlayerClient: {
2500
+ /**
2501
+ * The format type of the data.
2502
+ */
2503
+ type: "NBT",
2504
+ },
2505
+ /**
2506
+ * The data for an entity in the world.
2507
+ *
2508
+ * @see {@link NBTSchemas.nbtSchemas.ActorPrefix}
2509
+ */
2510
+ ActorPrefix: {
2511
+ /**
2512
+ * The format type of the data.
2513
+ */
2514
+ type: "NBT",
2515
+ },
2516
+ /**
2517
+ * A list of entity IDs in a chunk.
2518
+ *
2519
+ * These IDs are the ones used in the entities' LevelDB keys.
2520
+ *
2521
+ * The IDs are 32-bit signed integers.
2522
+ */
2523
+ Digest: {
2524
+ /**
2525
+ * The format type of the data.
2526
+ */
2527
+ type: "custom",
2528
+ /**
2529
+ * The format type that results from the {@link entryContentTypeToFormatMap.Digest.parse | parse} method.
2530
+ */
2531
+ resultType: "JSONNBT",
2532
+ /**
2533
+ * The function to parse the data.
2534
+ *
2535
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
2536
+ *
2537
+ * @param data The data to parse, as a buffer.
2538
+ * @returns A promise that resolves with the parsed data.
2539
+ *
2540
+ * @throws {any} If an error occurs while parsing the data.
2541
+ */
2542
+ async parse(data: Buffer): Promise<NBTSchemas.NBTSchemaTypes.Digest> {
2543
+ const entityIds: [high: number, low: number][] = [];
2544
+ for (let i = 0; i < data.length; i += 8) {
2545
+ entityIds.push([data.readInt32LE(i), data.readInt32LE(i + 4)]);
2546
+ }
2547
+ return {
2548
+ type: "compound",
2549
+ value: {
2550
+ entityIds: {
2551
+ type: "list",
2552
+ value: {
2553
+ type: "long",
2554
+ value: entityIds,
2555
+ },
2556
+ },
2557
+ },
2558
+ };
2559
+ },
2560
+ /**
2561
+ * The function to serialize the data.
2562
+ *
2563
+ * This result of this can be written directly to the file or LevelDB entry.
2564
+ *
2565
+ * @param data The data to serialize.
2566
+ * @returns The serialized data, as a buffer.
2567
+ *
2568
+ * @throws {any} If an error occurs while parsing the data.
2569
+ */
2570
+ serialize(data: NBTSchemas.NBTSchemaTypes.Digest): Buffer<ArrayBuffer> {
2571
+ const rawData: Buffer<ArrayBuffer>[] = data.value.entityIds.value.value.map((entityIds: [high: number, low: number]): Buffer<ArrayBuffer> => {
2572
+ const buffer: Buffer<ArrayBuffer> = Buffer.alloc(8);
2573
+ buffer.writeInt32LE(entityIds[0], 0);
2574
+ buffer.writeInt32LE(entityIds[1], 4);
2575
+ return buffer;
2576
+ });
2577
+ return Buffer.concat(rawData);
2578
+ },
2579
+ },
2580
+ /**
2581
+ * The data for a map.
2582
+ *
2583
+ * This includes things such as location, image data, and decorations.
2584
+ *
2585
+ * @see {@link NBTSchemas.nbtSchemas.Map}
2586
+ */
2587
+ Map: {
2588
+ /**
2589
+ * The format type of the data.
2590
+ */
2591
+ type: "NBT",
2592
+ },
2593
+ /**
2594
+ * The content type of the `portals` LevelDB key, which stores portal data.
2595
+ *
2596
+ * @see {@link NBTSchemas.nbtSchemas.Portals}
2597
+ */
2598
+ Portals: {
2599
+ /**
2600
+ * The format type of the data.
2601
+ */
2602
+ type: "NBT",
2603
+ },
2604
+ /**
2605
+ * The content type of the `schedulerWT` LevelDB key, which stores wandering trader data.
2606
+ *
2607
+ * @see {@link NBTSchemas.nbtSchemas.SchedulerWT}
2608
+ */
2609
+ SchedulerWT: {
2610
+ /**
2611
+ * The format type of the data.
2612
+ */
2613
+ type: "NBT",
2614
+ },
2615
+ /**
2616
+ * Date for a structure (the kind saved by a structure block or the [`/structure`](https://minecraft.wiki/w/Commands/structure) command).
2617
+ *
2618
+ * @see {@link NBTSchemas.nbtSchemas.StructureTemplate}
2619
+ */
2620
+ StructureTemplate: {
2621
+ /**
2622
+ * The format type of the data.
2623
+ */
2624
+ type: "NBT",
2625
+ /**
2626
+ * The raw file extension of the data.
2627
+ */
2628
+ rawFileExtension: "mcstructure",
2629
+ },
2630
+ /**
2631
+ * A ticking area, either from an entity or the [`/tickingarea`](https://minecraft.wiki/w/Commands/tickingarea) command.
2632
+ *
2633
+ * @see {@link NBTSchemas.nbtSchemas.TickingArea}
2634
+ */
2635
+ TickingArea: {
2636
+ /**
2637
+ * The format type of the data.
2638
+ */
2639
+ type: "NBT",
2640
+ },
2641
+ /**
2642
+ * @deprecated Only used in versions < 1.5.0.
2643
+ *
2644
+ * @todo Add a description for this.
2645
+ *
2646
+ * @see https://github.com/robofinch/Prismarine-Anchor/blob/main/crates/bedrock/leveldb-entries/src/entries/flat_world_layers.rs
2647
+ */
2648
+ FlatWorldLayers: {
2649
+ /**
2650
+ * The format type of the data.
2651
+ */
2652
+ type: "ASCII",
2653
+ },
2654
+ /**
2655
+ * @deprecated It is unknown when this was removed, it was found in a Windows 10 Edition Beta v0.15.0 world.
2656
+ *
2657
+ * @todo Add a description for this.
2658
+ *
2659
+ * @see https://github.com/robofinch/Prismarine-Anchor/blob/main/crates/bedrock/leveldb-entries/src/entries/level_spawn_was_fixed.rs
2660
+ */
2661
+ LevelSpawnWasFixed: {
2662
+ /**
2663
+ * The format type of the data.
2664
+ */
2665
+ type: "UTF-8",
2666
+ /**
2667
+ * The default value to use when initializing a new entry.
2668
+ *
2669
+ * Value:
2670
+ * ```typescript
2671
+ * Buffer.from("True", "utf-8")
2672
+ * ```
2673
+ */
2674
+ defaultValue: Buffer.from("True", "utf-8"),
2675
+ },
2676
+ /**
2677
+ * Stores the location of a lodestone compass.
2678
+ *
2679
+ * @see {@link NBTSchemas.nbtSchemas.PositionTrackingDB}
2680
+ */
2681
+ PositionTrackingDB: {
2682
+ /**
2683
+ * The format type of the data.
2684
+ */
2685
+ type: "NBT",
2686
+ },
2687
+ /**
2688
+ * The last ID used for a lodestone compass.
2689
+ *
2690
+ * @see {@link NBTSchemas.nbtSchemas.PositionTrackingLastId}
2691
+ */
2692
+ PositionTrackingLastId: {
2693
+ /**
2694
+ * The format type of the data.
2695
+ */
2696
+ type: "NBT",
2697
+ },
2698
+ /**
2699
+ * The scoreboard data (ex. from the [`/scoreboard`](https://minecraft.wiki/w/Commands/scoreboard) command).
2700
+ *
2701
+ * @see {@link NBTSchemas.nbtSchemas.Scoreboard}
2702
+ */
2703
+ Scoreboard: {
2704
+ /**
2705
+ * The format type of the data.
2706
+ */
2707
+ type: "NBT",
2708
+ },
2709
+ /**
2710
+ * The structure data of the Overworld dimension.
2711
+ *
2712
+ * This content type coexists with the {@link entryContentTypeToFormatMap.Overworld | Overworld} content type in some versions (such as v1.0.0.16 and v1.1.5.0).
2713
+ *
2714
+ * @deprecated It is unknown when this was removed, it was found in a Windows 10 Edition Beta v0.15.0 world.
2715
+ *
2716
+ * @see {@link NBTSchemas.nbtSchemas.LegacyOverworld}
2717
+ */
2718
+ LegacyOverworld: {
2719
+ /**
2720
+ * The format type of the data.
2721
+ */
2722
+ type: "NBT",
2723
+ },
2724
+ /**
2725
+ * The structure data of the Nether dimension.
2726
+ *
2727
+ * This content type coexists with the {@link entryContentTypeToFormatMap.Nether | Nether} content type in some versions (such as v1.0.0.16 and v1.1.5.0).
2728
+ *
2729
+ * @deprecated It is unknown when this was removed, it was found in a Windows 10 Edition Beta v0.15.0 world.
2730
+ *
2731
+ * @see {@link NBTSchemas.nbtSchemas.LegacyNether}
2732
+ */
2733
+ LegacyNether: {
2734
+ /**
2735
+ * The format type of the data.
2736
+ */
2737
+ type: "NBT",
2738
+ },
2739
+ /**
2740
+ * The structure data of the End dimension.
2741
+ *
2742
+ * This content type coexists with the {@link entryContentTypeToFormatMap.TheEnd | TheEnd} content type in some versions (such as v1.0.0.16 and v1.1.5.0).
2743
+ *
2744
+ * @deprecated It is unknown when this was removed, it was found in a Windows 10 Edition Beta v0.15.0 world.
2745
+ *
2746
+ * @see {@link NBTSchemas.nbtSchemas.LegacyTheEnd}
2747
+ */
2748
+ LegacyTheEnd: {
2749
+ /**
2750
+ * The format type of the data.
2751
+ */
2752
+ type: "NBT",
2753
+ },
2754
+ /**
2755
+ * The data of the Overworld dimension.
2756
+ *
2757
+ * @see {@link NBTSchemas.nbtSchemas.Overworld}
2758
+ */
2759
+ Overworld: {
2760
+ /**
2761
+ * The format type of the data.
2762
+ */
2763
+ type: "NBT",
2764
+ },
2765
+ /**
2766
+ * The data of the Nether dimension.
2767
+ *
2768
+ * @see {@link NBTSchemas.nbtSchemas.Nether}
2769
+ */
2770
+ Nether: {
2771
+ /**
2772
+ * The format type of the data.
2773
+ */
2774
+ type: "NBT",
2775
+ },
2776
+ /**
2777
+ * The data of the End dimension.
2778
+ *
2779
+ * @see {@link NBTSchemas.nbtSchemas.TheEnd}
2780
+ */
2781
+ TheEnd: {
2782
+ /**
2783
+ * The format type of the data.
2784
+ */
2785
+ type: "NBT",
2786
+ },
2787
+ /**
2788
+ * The data of a custom dimension.
2789
+ *
2790
+ * @see {@link NBTSchemas.nbtSchemas.CustomDimension}
2791
+ */
2792
+ CustomDimension: {
2793
+ /**
2794
+ * The format type of the data.
2795
+ */
2796
+ type: "NBT",
2797
+ },
2798
+ /**
2799
+ * The content type of the `AutonomousEntities` LevelDB key, which stores a list of autonomous entities.
2800
+ *
2801
+ * @see {@link NBTSchemas.nbtSchemas.AutonomousEntities}
2802
+ *
2803
+ * @todo Add a better description for this, that includes what an autonomous entity is.
2804
+ */
2805
+ AutonomousEntities: {
2806
+ /**
2807
+ * The format type of the data.
2808
+ */
2809
+ type: "NBT",
2810
+ },
2811
+ /**
2812
+ * The content type of the `BiomeData` LevelDB key, which stores data for certain biome types.
2813
+ *
2814
+ * @see {@link NBTSchemas.nbtSchemas.BiomeData}
2815
+ */
2816
+ BiomeData: {
2817
+ /**
2818
+ * The format type of the data.
2819
+ */
2820
+ type: "NBT",
2821
+ },
2822
+ /**
2823
+ * The content type of the `BiomeIdsTable` LevelDB key, which stores the numeric ID mappings for custom biomes.
2824
+ *
2825
+ * @see {@link NBTSchemas.nbtSchemas.BiomeIdsTable}
2826
+ */
2827
+ BiomeIdsTable: {
2828
+ /**
2829
+ * The format type of the data.
2830
+ */
2831
+ type: "NBT",
2832
+ },
2833
+ /**
2834
+ * The content type of the `DimensionNameIdTable` LevelDB key, which stores the numeric ID mappings for custom biomes.
2835
+ *
2836
+ * @see {@link NBTSchemas.nbtSchemas.DimensionNameIdTable}
2837
+ */
2838
+ DimensionNameIdTable: {
2839
+ /**
2840
+ * The format type of the data.
2841
+ */
2842
+ type: "NBT",
2843
+ },
2844
+ /**
2845
+ * The content type of the `mobevents` LevelDB key, which stores what mob events are enabled or disabled.
2846
+ *
2847
+ * @see {@link NBTSchemas.nbtSchemas.MobEvents}
2848
+ */
2849
+ MobEvents: {
2850
+ /**
2851
+ * The format type of the data.
2852
+ */
2853
+ type: "NBT",
2854
+ // TO-DO: Add a default value for this.
2855
+ },
2856
+ /**
2857
+ * The content type of the `level.dat` and `level.dat_old` files.
2858
+ *
2859
+ * This stores all the world settings.
2860
+ *
2861
+ * @see {@link NBTSchemas.nbtSchemas.LevelDat}
2862
+ */
2863
+ LevelDat: {
2864
+ /**
2865
+ * The format type of the data.
2866
+ */
2867
+ type: "custom",
2868
+ /**
2869
+ * The format type that results from the {@link entryContentTypeToFormatMap.LevelDat.parse | parse} method.
2870
+ */
2871
+ resultType: "JSONNBT",
2872
+ /**
2873
+ * The function to parse the data.
2874
+ *
2875
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
2876
+ *
2877
+ * @param data The data to parse, as a buffer.
2878
+ * @returns A promise that resolves with the parsed data.
2879
+ *
2880
+ * @throws {any} If an error occurs while parsing the data.
2881
+ */
2882
+ async parse(data: Buffer): Promise<NBTSchemas.NBTSchemaTypes.LevelDat> {
2883
+ return (await NBT.parse(data)).parsed;
2884
+ },
2885
+ /**
2886
+ * The function to serialize the data.
2887
+ *
2888
+ * This result of this can be written directly to the file or LevelDB entry.
2889
+ *
2890
+ * @param data The data to serialize.
2891
+ * @returns The serialized data, as a buffer.
2892
+ *
2893
+ * @throws {TypeError} If {@link data} has a name property at the top level that is not of type string.
2894
+ */
2895
+ serialize(data: NBTSchemas.NBTSchemaTypes.LevelDat): Buffer<ArrayBuffer> {
2896
+ const nbtData: Buffer = NBT.writeUncompressed({ name: "", ...data }, "little");
2897
+ return Buffer.concat([Buffer.from("0A000000", "hex"), writeSpecificIntType(Buffer.alloc(4), BigInt(nbtData.length), 4, "LE", false), nbtData]);
2898
+ },
2899
+ /**
2900
+ * The default value to use when initializing a new entry.
2901
+ *
2902
+ * @todo Add a link to the object with the default level.dat value once it is made.
2903
+ */
2904
+ get defaultValue(): Buffer<ArrayBuffer> {
2905
+ // TO-DO: Add a full default level.dat value.
2906
+ const nbtData: Buffer = NBT.writeUncompressed(
2907
+ { name: "", type: "compound", value: {} } as /* @todo Remove this partial later. */ Partial<NBTSchemas.NBTSchemaTypes.LevelDat> & NBT.NBT,
2908
+ "little"
2909
+ );
2910
+ const result: Buffer<ArrayBuffer> = Buffer.concat([
2911
+ Buffer.from("0A000000", "hex"),
2912
+ writeSpecificIntType(Buffer.alloc(4), BigInt(nbtData.length), 4, "LE", false),
2913
+ nbtData,
2914
+ ]);
2915
+ Object.defineProperty(this, "defaultValue", { value: result, configurable: true, enumerable: true, writable: false });
2916
+ return result;
2917
+ },
2918
+ /**
2919
+ * The raw file extension of the data.
2920
+ */
2921
+ rawFileExtension: "dat",
2922
+ },
2923
+ /**
2924
+ * The content type of the `WorldClocks` LevelDB key, which serves an unknown purpose.
2925
+ *
2926
+ * @see {@link NBTSchemas.nbtSchemas.WorldClocks}
2927
+ *
2928
+ * @since 1.26.10 (maybe 1.26.0)
2929
+ *
2930
+ * @todo Figure out that this is used for.
2931
+ * @todo Figure out what preview and full release this was actually added in.
2932
+ */
2933
+ WorldClocks: {
2934
+ /**
2935
+ * The format type of the data.
2936
+ */
2937
+ type: "NBT",
2938
+ /**
2939
+ * The default value to use when initializing a new entry.
2940
+ *
2941
+ * Value:
2942
+ * ```json
2943
+ * {
2944
+ * type: "compound",
2945
+ * name: "",
2946
+ * value: {
2947
+ * clocks: {
2948
+ * type: "list",
2949
+ * value: {
2950
+ * type: "end",
2951
+ * value: [],
2952
+ * },
2953
+ * },
2954
+ * },
2955
+ * }
2956
+ * ```
2957
+ */
2958
+ defaultValue: NBT.writeUncompressed(
2959
+ {
2960
+ type: "compound",
2961
+ name: "",
2962
+ value: {
2963
+ clocks: {
2964
+ type: "list",
2965
+ value: {
2966
+ type: "end" as NBT.TagType,
2967
+ value: [],
2968
+ },
2969
+ },
2970
+ },
2971
+ },
2972
+ "little"
2973
+ ),
2974
+ },
2975
+ /**
2976
+ * Bounding boxes for structures, including structure spawns (such as a Pillager Outpost)
2977
+ * and volumes where mobs cannot spawn through the normal biome-based means (such as
2978
+ * Trial Chambers).
2979
+ *
2980
+ * @see {@link NBTSchemas.nbtSchemas.AABBVolumes}
2981
+ *
2982
+ * @since 1.21.20 (or some 1.21.20 preview)
2983
+ */
2984
+ AABBVolumes: {
2985
+ /**
2986
+ * The format type of the data.
2987
+ */
2988
+ type: "custom",
2989
+ /**
2990
+ * The format type that results from the {@link entryContentTypeToFormatMap.AABBVolumes.parse | parse} method.
2991
+ */
2992
+ resultType: "JSONNBT",
2993
+ /**
2994
+ * The function to parse the data.
2995
+ *
2996
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
2997
+ *
2998
+ * @param data The data to parse, as a buffer.
2999
+ * @returns The parsed data.
3000
+ *
3001
+ * @throws {RangeError} If the buffer is less than 4 bytes.
3002
+ * @throws {unknown} If an error occurs while parsing the data.
3003
+ */
3004
+ parse(data: Buffer): NBTSchemas.NBTSchemaTypes.AABBVolumes {
3005
+ if (data.length < 4) throw new RangeError("AABBVolumes buffer must be at least 4 bytes long");
3006
+
3007
+ const version: number = data.readUInt32LE(0);
3008
+
3009
+ if (version !== 1) throw new Error(`Unsupported AABBVolumes version: ${version}`);
3010
+
3011
+ let offset = 4;
3012
+
3013
+ const structureTypeCount: number = data.readUInt32LE(offset);
3014
+ const structureTypes: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["StructureTypes"]["value"]["value"] = new Array(structureTypeCount);
3015
+ offset += 4;
3016
+
3017
+ for (let i = 0; i < structureTypeCount; i++) {
3018
+ const structureTypeLength: number = data.readUInt16LE(offset + 4);
3019
+ const structureType: string = data.toString("utf-8", offset + 6, offset + 6 + structureTypeLength);
3020
+ structureTypes[i] = {
3021
+ Id: { type: "int", value: data.readUInt32LE(offset) },
3022
+ Type: { type: "string", value: structureType as (typeof structureTypes)[number]["Type"]["value"] },
3023
+ };
3024
+ offset += 6 + structureTypeLength;
3025
+ }
3026
+
3027
+ const chunkBoundingBoxCount: number = data.readUInt32LE(offset);
3028
+ const chunkBoundingBoxes: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["ChunkBoundingBoxes"]["value"]["value"] = new Array(chunkBoundingBoxCount);
3029
+ offset += 4;
3030
+
3031
+ for (let i = 0; i < chunkBoundingBoxCount; i++, offset += 28) {
3032
+ chunkBoundingBoxes[i] = {
3033
+ Id: { type: "int", value: data.readInt32LE(offset) },
3034
+ AABBMinX: { type: "int", value: data.readInt32LE(offset + 4) },
3035
+ AABBMinY: { type: "int", value: data.readInt32LE(offset + 8) },
3036
+ AABBMinZ: { type: "int", value: data.readInt32LE(offset + 12) },
3037
+ AABBMaxX: { type: "int", value: data.readInt32LE(offset + 16) },
3038
+ AABBMaxY: { type: "int", value: data.readInt32LE(offset + 20) },
3039
+ AABBMaxZ: { type: "int", value: data.readInt32LE(offset + 24) },
3040
+ };
3041
+ }
3042
+
3043
+ const dynamicSpawnAreaCount: number = data.readUInt32LE(offset);
3044
+ const dynamicSpawnAreas: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["DynamicSpawnAreas"]["value"]["value"] = new Array(dynamicSpawnAreaCount);
3045
+ offset += 4;
3046
+
3047
+ for (let i = 0; i < dynamicSpawnAreaCount; i++, offset += 12) {
3048
+ dynamicSpawnAreas[i] = {
3049
+ BoundingBoxId: { type: "int", value: data.readUInt32LE(offset) },
3050
+ StructureId: { type: "int", value: data.readUInt32LE(offset + 4) },
3051
+ FullBoundingBox: { type: "int", value: data.readUInt32LE(offset + 8) as 0 | 1 },
3052
+ };
3053
+ }
3054
+
3055
+ const staticSpawnAreaCount: number = data.readUInt32LE(offset);
3056
+ const staticSpawnAreas: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["StaticSpawnAreas"]["value"]["value"] = new Array(staticSpawnAreaCount);
3057
+ offset += 4;
3058
+
3059
+ for (let i = 0; i < staticSpawnAreaCount; i++, offset += 16) {
3060
+ staticSpawnAreas[i] = {
3061
+ BoundingBoxId: { type: "int", value: data.readUInt32LE(offset) },
3062
+ StructureId: { type: "int", value: data.readUInt32LE(offset + 4) },
3063
+ HeightDifference: { type: "int", value: data.readInt32LE(offset + 8) },
3064
+ FullBoundingBox: { type: "int", value: data.readUInt32LE(offset + 12) as 0 | 1 },
3065
+ };
3066
+ }
3067
+
3068
+ return {
3069
+ type: "compound",
3070
+ value: {
3071
+ version: { type: "int", value: version },
3072
+ StructureTypes: { type: "list", value: { type: "compound", value: structureTypes } },
3073
+ ChunkBoundingBoxes: {
3074
+ type: "list",
3075
+ value: {
3076
+ type: "compound",
3077
+ value: chunkBoundingBoxes,
3078
+ },
3079
+ },
3080
+ DynamicSpawnAreas: {
3081
+ type: "list",
3082
+ value: {
3083
+ type: "compound",
3084
+ value: dynamicSpawnAreas,
3085
+ },
3086
+ },
3087
+ StaticSpawnAreas: {
3088
+ type: "list",
3089
+ value: {
3090
+ type: "compound",
3091
+ value: staticSpawnAreas,
3092
+ },
3093
+ },
3094
+ },
3095
+ };
3096
+ },
3097
+ /**
3098
+ * The function to serialize the data.
3099
+ *
3100
+ * This result of this can be written directly to the file or LevelDB entry.
3101
+ *
3102
+ * @param data The data to serialize.
3103
+ * @returns The serialized data, as a buffer.
3104
+ *
3105
+ * @throws {TypeError} If one of the StructureTypes entries are undefined.
3106
+ * @throws {TypeError} If one of the ChunkBoundingBoxes entries are undefined.
3107
+ * @throws {TypeError} If one of the DynamicSpawnAreas entries are undefined.
3108
+ * @throws {TypeError} If one of the StaticSpawnAreas entries are undefined.
3109
+ * @throws {unknown} If an error occurs while serializing the data.
3110
+ */
3111
+ serialize(data: NBTSchemas.NBTSchemaTypes.AABBVolumes): Buffer<ArrayBuffer> {
3112
+ const structureTypeCount: number = data.value.StructureTypes.value.value.length;
3113
+ const chunkBoundingBoxCount: number = data.value.ChunkBoundingBoxes.value.value.length;
3114
+ const dynamicSpawnAreaCount: number = data.value.DynamicSpawnAreas.value.value.length;
3115
+ const staticSpawnAreaCount: number = data.value.StaticSpawnAreas.value.value.length;
3116
+ const serializedData: Buffer<ArrayBuffer> = Buffer.alloc(
3117
+ 20 +
3118
+ structureTypeCount * 6 +
3119
+ data.value.StructureTypes.value.value.reduce(
3120
+ (a: number, b: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["StructureTypes"]["value"]["value"][number]): number =>
3121
+ a + b.Type.value.length,
3122
+ 0
3123
+ ) +
3124
+ chunkBoundingBoxCount * 28 +
3125
+ dynamicSpawnAreaCount * 12 +
3126
+ staticSpawnAreaCount * 16
3127
+ );
3128
+
3129
+ serializedData.writeUInt32LE(data.value.version.value, 0);
3130
+
3131
+ const structureTypes: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["StructureTypes"]["value"]["value"] = data.value.StructureTypes.value.value;
3132
+
3133
+ let offset = 4;
3134
+
3135
+ serializedData.writeUInt32LE(structureTypeCount, offset);
3136
+ offset += 4;
3137
+
3138
+ for (let i = 0; i < structureTypeCount; i++) {
3139
+ const entry: (typeof structureTypes)[number] | undefined = structureTypes[i];
3140
+ if (entry === undefined) throw new TypeError(`StructureTypes entry is undefined at index ${i}`);
3141
+
3142
+ const value: string = entry.Type.value;
3143
+ serializedData.writeUInt32LE(entry.Id.value, offset);
3144
+ const stringByteLength: number = serializedData.write(value, offset + 6, "utf-8");
3145
+ serializedData.writeUInt16LE(stringByteLength, offset + 4);
3146
+ offset += 6 + stringByteLength;
3147
+ }
3148
+
3149
+ const chunkBoundingBoxes: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["ChunkBoundingBoxes"]["value"]["value"] =
3150
+ data.value.ChunkBoundingBoxes.value.value;
3151
+
3152
+ serializedData.writeUInt32LE(chunkBoundingBoxCount, offset);
3153
+ offset += 4;
3154
+
3155
+ for (let i = 0; i < chunkBoundingBoxCount; i++, offset += 28) {
3156
+ const entry: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["ChunkBoundingBoxes"]["value"]["value"][number] | undefined = chunkBoundingBoxes[i];
3157
+ if (!entry) throw new TypeError(`ChunkBoundingBoxes entry is undefined at index ${i}`);
3158
+
3159
+ serializedData.writeUInt32LE(entry.Id.value, offset);
3160
+ serializedData.writeInt32LE(entry.AABBMinX.value, offset + 4);
3161
+ serializedData.writeInt32LE(entry.AABBMinY.value, offset + 8);
3162
+ serializedData.writeInt32LE(entry.AABBMinZ.value, offset + 12);
3163
+ serializedData.writeInt32LE(entry.AABBMaxX.value, offset + 16);
3164
+ serializedData.writeInt32LE(entry.AABBMaxY.value, offset + 20);
3165
+ serializedData.writeInt32LE(entry.AABBMaxZ.value, offset + 24);
3166
+ }
3167
+
3168
+ const dynamicSpawnAreas: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["DynamicSpawnAreas"]["value"]["value"] =
3169
+ data.value.DynamicSpawnAreas.value.value;
3170
+
3171
+ serializedData.writeUInt32LE(dynamicSpawnAreaCount, offset);
3172
+ offset += 4;
3173
+
3174
+ for (let i = 0; i < dynamicSpawnAreaCount; i++, offset += 12) {
3175
+ const entry: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["DynamicSpawnAreas"]["value"]["value"][number] | undefined = dynamicSpawnAreas[i];
3176
+ if (!entry) throw new TypeError(`DynamicSpawnAreas entry is undefined at index ${i}`);
3177
+
3178
+ serializedData.writeUInt32LE(entry.BoundingBoxId.value, offset);
3179
+ serializedData.writeUInt32LE(entry.StructureId.value, offset + 4);
3180
+ serializedData.writeUInt32LE(entry.FullBoundingBox.value, offset + 8);
3181
+ }
3182
+
3183
+ const staticSpawnAreas: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["StaticSpawnAreas"]["value"]["value"] =
3184
+ data.value.StaticSpawnAreas.value.value;
3185
+
3186
+ serializedData.writeUInt32LE(staticSpawnAreaCount, offset);
3187
+ offset += 4;
3188
+
3189
+ for (let i = 0; i < staticSpawnAreaCount; i++, offset += 16) {
3190
+ const entry: NBTSchemas.NBTSchemaTypes.AABBVolumes["value"]["StaticSpawnAreas"]["value"]["value"][number] | undefined = staticSpawnAreas[i];
3191
+ if (!entry) throw new TypeError(`StaticSpawnAreas entry is undefined at index ${i}`);
3192
+
3193
+ serializedData.writeUInt32LE(entry.BoundingBoxId.value, offset);
3194
+ serializedData.writeUInt32LE(entry.StructureId.value, offset + 4);
3195
+ serializedData.writeInt32LE(entry.HeightDifference.value, offset + 8);
3196
+ serializedData.writeUInt32LE(entry.FullBoundingBox.value, offset + 12);
3197
+ }
3198
+
3199
+ return serializedData;
3200
+ },
3201
+ },
3202
+ /**
3203
+ * The content type of the `DynamicProperties` LevelDB key, which stores dynamic properties data for add-ons.
3204
+ *
3205
+ * @see {@link NBTSchemas.nbtSchemas.DynamicProperties}
3206
+ */
3207
+ DynamicProperties: {
3208
+ /**
3209
+ * The format type of the data.
3210
+ */
3211
+ type: "NBT",
3212
+ /**
3213
+ * The default value to use when initializing a new entry.
3214
+ *
3215
+ * Value:
3216
+ * ```json
3217
+ * { "name": "", "type": "compound", "value": {} }
3218
+ * ```
3219
+ */
3220
+ defaultValue: NBT.writeUncompressed({ name: "", type: "compound", value: {} }, "little"),
3221
+ },
3222
+ /**
3223
+ * Stores the NBT metadata of all chunks. Maps the xxHash64 hash of NBT data
3224
+ * to that NBT data, so that each chunk need only store 8 bytes instead of the entire
3225
+ * NBT; most chunks have the same metadata.
3226
+ *
3227
+ * The first 4 bytes represent the number of entries as a 32-bit little-endian integer (it is unknown if it is signed or not).
3228
+ *
3229
+ * The first 4 bytes are followed by multiple chunks of data formatted as the 8 byte hash of the NBT data plus the NBT compound.
3230
+ *
3231
+ * ```
3232
+ * {BYTEx4}{BYTEx8}{NBTCompound}{BYTEx8}{NBTCompound}{BYTEx8}{NBTCompound}{BYTEx8}{NBTCompound}
3233
+ * ```
3234
+ *
3235
+ * @see {@link NBTSchemas.nbtSchemas.LevelChunkMetaDataDictionary}
3236
+ */
3237
+ LevelChunkMetaDataDictionary: {
3238
+ // https://github.com/MiemieMethod/bedrock-docs/blob/8cd37dacbd064f5fb2a4953548739a258b31dd21/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L501
3239
+ /**
3240
+ * The format type of the data.
3241
+ */
3242
+ type: "custom",
3243
+ resultType: "JSONNBT",
3244
+ // TODO: Make an NBT schema for the `LevelChunkMetaDataDictionary` format and use it as the return type here.
3245
+ async parse(data: Buffer): Promise<NBTSchemas.NBTSchemaTypes.LevelChunkMetaDataDictionary> {
3246
+ const parsedData: NBTSchemas.NBTSchemaTypes.LevelChunkMetaDataDictionary["value"] = {};
3247
+ for (let i = 12; i < data.length; i += 8) {
3248
+ const hash = data.subarray(i - 8, i);
3249
+ // TODO: Figure out how to use parseUncompressed in this situation to make it sync while still being able to get the size offset.
3250
+ const parsed = await NBT.parse(data.subarray(i), "little");
3251
+ // parsedData.push([data.slice(i - 8, i), parsed]);
3252
+ parsedData[hash.toString("hex")] = parsed.parsed as unknown as NBTSchemas.NBTSchemaTypes.LevelChunkMetaDataDictionary["value"][string];
3253
+ i += parsed.metadata.size;
3254
+ }
3255
+ return { type: "compound", value: parsedData };
3256
+ },
3257
+ serialize(data: NBTSchemas.NBTSchemaTypes.LevelChunkMetaDataDictionary): Buffer<ArrayBuffer> {
3258
+ // TODO: Find a way to properly hash the NBT data, since the `xxhashjs` and `xxhash-wasm` modules don't match up with the hashes generated by the game.
3259
+ const entryCountBuffer: Buffer<ArrayBuffer> = Buffer.alloc(4);
3260
+ entryCountBuffer.writeUInt32LE(Object.keys(data.value).length, 0);
3261
+ const dataBuffers: Buffer[] = [entryCountBuffer];
3262
+ for (const [hashHex, value] of Object.entries(data.value)) {
3263
+ if (!/^[0-9a-f]{16}$/i.test(hashHex)) throw new TypeError(`Invalid hash: ${hashHex}`);
3264
+ const hash: Buffer<ArrayBuffer> = Buffer.from(hashHex, "hex");
3265
+ const nbtBuffer: Buffer = NBT.writeUncompressed({ name: "", ...value }, "little");
3266
+ dataBuffers.push(hash);
3267
+ dataBuffers.push(nbtBuffer);
3268
+ }
3269
+ return Buffer.concat(dataBuffers);
3270
+ },
3271
+ },
3272
+ /**
3273
+ * @todo Figure out how to parse this. (It seems that each one just has a value of 1 (`0x31`). It also seems that the data is actually based on the key, which has an id that can be used with the realms API to get the corresponding data.)
3274
+ * @todo Add a description for this.
3275
+ */
3276
+ RealmsStoriesData: {
3277
+ /**
3278
+ * The format type of the data.
3279
+ */
3280
+ type: "unknown",
3281
+ },
3282
+ /**
3283
+ * The content type used for LevelDB keys that are used for the forced world corruption feature of the developer version of Bedrock Edition.
3284
+ *
3285
+ * These keys are normally only found when the [`/corruptworld`](https://minecraft.wiki/w/Commands/corruptworld) command is used.
3286
+ *
3287
+ * Removing these keys fixes the forced world corruption.
3288
+ */
3289
+ ForcedWorldCorruption: {
3290
+ /**
3291
+ * The format type of the data.
3292
+ */
3293
+ type: "UTF-8",
3294
+ /**
3295
+ * The default value to use when initializing a new entry.
3296
+ *
3297
+ * Value:
3298
+ * ```typescript
3299
+ * Buffer.from("true", "utf-8")
3300
+ * ```
3301
+ */
3302
+ defaultValue: Buffer.from("true", "utf-8"),
3303
+ },
3304
+ /**
3305
+ * @todo Add a description for this.
3306
+ *
3307
+ * @see {@link NBTSchemas.nbtSchemas.ChunkLoadedRequest}
3308
+ *
3309
+ * @see https://github.com/MiemieMethod/bedrock-docs/blob/main/.knowledge/wiki%E6%91%98%E5%BD%95/%E4%B8%AD%E6%96%87Minecraft%20Wiki/%E5%9F%BA%E5%B2%A9%E7%89%88LevelDB%E6%A0%BC%E5%BC%8F.wikitext#L1175
3310
+ */
3311
+ ChunkLoadedRequest: {
3312
+ /**
3313
+ * The format type of the data.
3314
+ */
3315
+ type: "NBT",
3316
+ // TO-DO: Add a default value for this.
3317
+ },
3318
+ /**
3319
+ * All data that has a key that is not recognized.
3320
+ */
3321
+ Unknown: {
3322
+ /**
3323
+ * The format type of the data.
3324
+ */
3325
+ type: "unknown",
3326
+ },
3327
+ } as const satisfies {
3328
+ [key in DBEntryContentType]: EntryContentTypeFormatData;
3329
+ };
3330
+
3331
+ /**
3332
+ * The format data for an entry content type.
3333
+ *
3334
+ * Use this type if you want to have support for content type formats that aren't currently is use but may be in the future.
3335
+ *
3336
+ * This is used in the {@link entryContentTypeToFormatMap} object.
3337
+ */
3338
+ export type EntryContentTypeFormatData = (
3339
+ | {
3340
+ /**
3341
+ * The format type of the data.
3342
+ */
3343
+ readonly type: "JSON" | "SNBT" | "unknown";
3344
+ }
3345
+ | {
3346
+ /**
3347
+ * The format type of the data.
3348
+ */
3349
+ readonly type: "ASCII" | "binary" | "binaryPlainText" | "UTF-8";
3350
+ /**
3351
+ * The minimum length of the data.
3352
+ *
3353
+ * If not present, `0` should be assumed.
3354
+ *
3355
+ * @default 0
3356
+ */
3357
+ readonly minLength?: number;
3358
+ /**
3359
+ * The maximum length of the data.
3360
+ *
3361
+ * If not present, `Infinity` should be assumed.
3362
+ *
3363
+ * @default Infinity
3364
+ */
3365
+ readonly maxLength?: number;
3366
+ // IDEA: Add a validation function.
3367
+ }
3368
+ | {
3369
+ /**
3370
+ * The format type of the data.
3371
+ */
3372
+ readonly type: "hex";
3373
+ /**
3374
+ * The minimum length of the data in bytes (if the hex data is a string of hexadecimal characters, two characters is one byte).
3375
+ *
3376
+ * If not present, `0` should be assumed.
3377
+ *
3378
+ * @default 0
3379
+ */
3380
+ readonly minLength?: number;
3381
+ /**
3382
+ * The maximum length of the data in bytes (if the hex data is a string of hexadecimal characters, two characters is one byte).
3383
+ *
3384
+ * If not present, `Infinity` should be assumed.
3385
+ *
3386
+ * @default Infinity
3387
+ */
3388
+ readonly maxLength?: number;
3389
+ // IDEA: Add a validation function.
3390
+ }
3391
+ | {
3392
+ /**
3393
+ * The format type of the data.
3394
+ */
3395
+ readonly type: "NBT";
3396
+ /**
3397
+ * The endianness of the data.
3398
+ *
3399
+ * If not present, `"LE"` should be assumed.
3400
+ *
3401
+ * - `"BE"`: Big Endian
3402
+ * - `"LE"`: Little Endian
3403
+ * - `"LEV"`: Little Varint
3404
+ *
3405
+ * @default "LE"
3406
+ */
3407
+ readonly format?: "BE" | "LE" | "LEV";
3408
+ }
3409
+ | {
3410
+ /**
3411
+ * The format type of the data.
3412
+ */
3413
+ readonly type: "int";
3414
+ /**
3415
+ * How many bytes this integer is.
3416
+ */
3417
+ readonly bytes: number;
3418
+ /**
3419
+ * The endianness of the data.
3420
+ */
3421
+ readonly format: "BE" | "LE";
3422
+ /**
3423
+ * The signedness of the data.
3424
+ */
3425
+ readonly signed: boolean;
3426
+ }
3427
+ | {
3428
+ /**
3429
+ * The format type of the data.
3430
+ */
3431
+ readonly type: "custom";
3432
+ /**
3433
+ * The format type that results from the {@link parse} method.
3434
+ */
3435
+ readonly resultType: "JSONNBT";
3436
+ /**
3437
+ * The function to parse the data.
3438
+ *
3439
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
3440
+ *
3441
+ * @param data The data to parse, as a buffer.
3442
+ * @returns The parsed data.
3443
+ */
3444
+ parse(data: Buffer): NBT.Compound | Promise<NBT.Compound>;
3445
+ /**
3446
+ * The function to serialize the data.
3447
+ *
3448
+ * This result of this can be written directly to the file or LevelDB entry.
3449
+ *
3450
+ * @param data The data to serialize.
3451
+ * @returns The serialized data, as a buffer.
3452
+ */
3453
+ serialize(data: NBT.Compound): Buffer | Promise<Buffer>;
3454
+ }
3455
+ | {
3456
+ /**
3457
+ * The format type of the data.
3458
+ */
3459
+ readonly type: "custom";
3460
+ /**
3461
+ * The format type that results from the {@link parse} method.
3462
+ */
3463
+ readonly resultType: "SNBT";
3464
+ /**
3465
+ * The function to parse the data.
3466
+ *
3467
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
3468
+ *
3469
+ * @param data The data to parse, as a buffer.
3470
+ * @returns The parsed data.
3471
+ */
3472
+ parse(data: Buffer): string | Promise<string>;
3473
+ /**
3474
+ * The function to serialize the data.
3475
+ *
3476
+ * This result of this can be written directly to the file or LevelDB entry.
3477
+ *
3478
+ * @param data The data to serialize.
3479
+ * @returns The serialized data, as a buffer.
3480
+ */
3481
+ serialize(data: string): Buffer | Promise<Buffer>;
3482
+ }
3483
+ | {
3484
+ /**
3485
+ * The format type of the data.
3486
+ */
3487
+ readonly type: "custom";
3488
+ /**
3489
+ * The format type that results from the {@link parse} method.
3490
+ */
3491
+ readonly resultType: "buffer";
3492
+ /**
3493
+ * The function to parse the data.
3494
+ *
3495
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
3496
+ *
3497
+ * @param data The data to parse, as a buffer.
3498
+ * @returns The parsed data.
3499
+ */
3500
+ parse(data: Buffer): Buffer | Promise<Buffer>;
3501
+ /**
3502
+ * The function to serialize the data.
3503
+ *
3504
+ * This result of this can be written directly to the file or LevelDB entry.
3505
+ *
3506
+ * @param data The data to serialize.
3507
+ * @returns The serialized data, as a buffer.
3508
+ */
3509
+ serialize(data: Buffer): Buffer | Promise<Buffer>;
3510
+ }
3511
+ | {
3512
+ /**
3513
+ * The format type of the data.
3514
+ */
3515
+ readonly type: "custom";
3516
+ /**
3517
+ * The format type that results from the {@link parse} method.
3518
+ */
3519
+ readonly resultType: "unknown";
3520
+ /**
3521
+ * The function to parse the data.
3522
+ *
3523
+ * The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
3524
+ *
3525
+ * @param data The data to parse, as a buffer.
3526
+ * @returns The parsed data.
3527
+ */
3528
+ parse(data: Buffer): any | Promise<any>;
3529
+ /**
3530
+ * The function to serialize the data.
3531
+ *
3532
+ * This result of this can be written directly to the file or LevelDB entry.
3533
+ *
3534
+ * @param data The data to serialize.
3535
+ * @returns The serialized data, as a buffer.
3536
+ */
3537
+ serialize(data: any): Buffer | Promise<Buffer>;
3538
+ }
3539
+ ) & {
3540
+ /**
3541
+ * The raw file extension of the data.
3542
+ *
3543
+ * If not present, `"bin"` should be assumed.
3544
+ *
3545
+ * @default "bin"
3546
+ */
3547
+ readonly rawFileExtension?: string;
3548
+ /**
3549
+ * The default value to use when initializing a new entry.
3550
+ *
3551
+ * @default undefined
3552
+ */
3553
+ readonly defaultValue?: Buffer;
3554
+ };
3555
+
3556
+ //#endregion
3557
+
3558
+ // --------------------------------------------------------------------------------
3559
+ // Types
3560
+ // --------------------------------------------------------------------------------
3561
+
3562
+ //#region Types
3563
+
3564
+ /**
3565
+ * Represents a two-directional vector.
3566
+ */
3567
+ export interface Vector2 {
3568
+ /**
3569
+ * X component of this vector.
3570
+ */
3571
+ x: number;
3572
+ /**
3573
+ * Y component of this vector.
3574
+ */
3575
+ y: number;
3576
+ }
3577
+
3578
+ /**
3579
+ * Represents a two-directional vector with X and Z components.
3580
+ */
3581
+ export interface VectorXZ {
3582
+ /**
3583
+ * X component of this vector.
3584
+ */
3585
+ x: number;
3586
+ /**
3587
+ * Z component of this vector.
3588
+ */
3589
+ z: number;
3590
+ }
3591
+
3592
+ /**
3593
+ * Represents a three-directional vector.
3594
+ */
3595
+ export interface Vector3 {
3596
+ /**
3597
+ * X component of this vector.
3598
+ */
3599
+ x: number;
3600
+ /**
3601
+ * Y component of this vector.
3602
+ */
3603
+ y: number;
3604
+ /**
3605
+ * Z component of this vector.
3606
+ */
3607
+ z: number;
3608
+ }
3609
+
3610
+ /**
3611
+ * An ID of a Minecraft dimension.
3612
+ */
3613
+ export type Dimension = (typeof dimensions)[number];
3614
+
3615
+ /**
3616
+ * Represents a three-directional vector with an associated dimension.
3617
+ */
3618
+ export interface DimensionLocation extends Vector3 {
3619
+ /**
3620
+ * Dimension that this coordinate is associated with.
3621
+ */
3622
+ dimension: Dimension | number;
3623
+ }
3624
+
3625
+ /**
3626
+ * Represents a two-directional vector with an associated dimension.
3627
+ */
3628
+ export interface DimensionVector2 extends Vector2 {
3629
+ /**
3630
+ * Dimension that this coordinate is associated with.
3631
+ */
3632
+ dimension: Dimension | number;
3633
+ }
3634
+
3635
+ /**
3636
+ * Represents a two-directional vector with X and Z components and an associated dimension.
3637
+ */
3638
+ export interface DimensionVectorXZ extends VectorXZ {
3639
+ /**
3640
+ * Dimension that this coordinate is associated with.
3641
+ */
3642
+ dimension: Dimension | number;
3643
+ }
3644
+
3645
+ /**
3646
+ * Represents a two-directional vector with X and Z components and an associated dimension and a sub-chunk index.
3647
+ */
3648
+ export interface SubChunkIndexDimensionVectorXZ extends DimensionVectorXZ {
3649
+ /**
3650
+ * The index of this sub-chunk.
3651
+ *
3652
+ * Should be between 0 and 15 (inclusive).
3653
+ */
3654
+ subChunkIndex: number;
3655
+ }
3656
+
3657
+ /**
3658
+ * @todo
3659
+ */
3660
+ export interface StructureSectionData extends NBT.Compound {
3661
+ /**
3662
+ * The size of the structure, as a tuple of 3 integers.
3663
+ */
3664
+ size: NBTSchemas.NBTSchemaTypes.StructureTemplate["value"]["size"];
3665
+ /**
3666
+ * The block indices.
3667
+ *
3668
+ * These are two arrays of indices in the block palette.
3669
+ *
3670
+ * The first layer is the block layer.
3671
+ *
3672
+ * The second layer is the waterlog layer, even though it is mainly used for waterlogging, other blocks can be put here to,
3673
+ * which allows for putting two blocks in the same location, or creating ghost blocks (as blocks in this layer cannot be interacted with,
3674
+ * however when the corresponding block in the block layer is broken, this block gets moved to the block layer).
3675
+ */
3676
+ block_indices: {
3677
+ type: `${NBT.TagType.List}`;
3678
+ value: {
3679
+ type: `${NBT.TagType.List}`;
3680
+ value: [
3681
+ blockLayer: {
3682
+ type: `${NBT.TagType.Int}`;
3683
+ value: number[];
3684
+ },
3685
+ waterlogLayer: {
3686
+ type: `${NBT.TagType.Int}`;
3687
+ value: number[];
3688
+ },
3689
+ ];
3690
+ };
3691
+ };
3692
+ /**
3693
+ * The block palette.
3694
+ */
3695
+ palette: {
3696
+ type: `${NBT.TagType.List}`;
3697
+ value: {
3698
+ type: `${NBT.TagType.Compound}`;
3699
+ value: NBTSchemas.NBTSchemaTypes.Block["value"][];
3700
+ };
3701
+ };
3702
+ }
3703
+
3704
+ /**
3705
+ * Biome palette data.
3706
+ */
3707
+ export interface BiomePalette {
3708
+ /**
3709
+ * The data for the individual blocks, `null` if this sub-chunk has no biome data and should not be written, or an empty array if this sub-chunk has no biome data and should be written.
3710
+ *
3711
+ * The values of this map to the index of the biome in the palette.
3712
+ */
3713
+ values: number[] | null;
3714
+ /**
3715
+ * The palette of biomes as a list of biome numeric IDs, or an empty array if this sub-chunk has no biome data.
3716
+ */
3717
+ palette: number[];
3718
+ }
3719
+
3720
+ /**
3721
+ * The content type of a LevelDB entry.
3722
+ */
3723
+ export type DBEntryContentType = (typeof DBEntryContentTypes)[number];
3724
+
3725
+ /**
3726
+ * A content type of a LevelDB chunk key entry.
3727
+ */
3728
+ export type DBChunkKeyEntryContentType =
3729
+ | "Data3D"
3730
+ | "Version"
3731
+ | "Data2D"
3732
+ | "Data2DLegacy"
3733
+ | "SubChunkPrefix"
3734
+ | "LegacyTerrain"
3735
+ | "BlockEntity"
3736
+ | "Entity"
3737
+ | "PendingTicks"
3738
+ | "LegacyBlockExtraData"
3739
+ | "BiomeState"
3740
+ | "FinalizedState"
3741
+ | "ConversionData"
3742
+ | "BorderBlocks"
3743
+ | "HardcodedSpawners"
3744
+ | "RandomTicks"
3745
+ | "Checksums"
3746
+ | "GenerationSeed"
3747
+ | "GeneratedPreCavesAndCliffsBlending"
3748
+ | "BlendingBiomeHeight"
3749
+ | "MetaDataHash"
3750
+ | "BlendingData"
3751
+ | "ActorDigestVersion"
3752
+ | "LegacyVersion"
3753
+ | "AABBVolumes";
3754
+
3755
+ /**
3756
+ * The a grouping type of LevelDB entry content types.
3757
+ */
3758
+ export type DBEntryContentTypeGroup = (typeof DBEntryContentTypesGrouping)[DBEntryContentType];
3759
+
3760
+ //#endregion
3761
+
3762
+ // --------------------------------------------------------------------------------
3763
+ // Functions
3764
+ // --------------------------------------------------------------------------------
3765
+
3766
+ //#region Functions
3767
+
3768
+ /**
3769
+ * Parses an integer from a buffer gives the number of bytes, endianness, signedness and offset.
3770
+ *
3771
+ * @param buffer The buffer to read from.
3772
+ * @param bytes The number of bytes to read.
3773
+ * @param format The endianness of the data.
3774
+ * @param signed The signedness of the data. Defaults to `false`.
3775
+ * @param offset The offset to read from. Defaults to `0`.
3776
+ * @returns The parsed integer.
3777
+ *
3778
+ * @throws {RangeError} If the byte length is less than 1.
3779
+ * @throws {RangeError} If the buffer does not contain enough data at the specified offset.
3780
+ */
3781
+ export function parseSpecificIntType(buffer: Buffer, bytes: number, format: "BE" | "LE", signed: boolean = false, offset: number = 0): bigint {
3782
+ if (bytes < 1) {
3783
+ throw new RangeError("Byte length must be at least 1");
3784
+ }
3785
+ if (offset + bytes > buffer.length) {
3786
+ throw new RangeError("Buffer does not contain enough data at the specified offset");
3787
+ }
3788
+
3789
+ let result: bigint = 0n;
3790
+
3791
+ if (format === "BE") {
3792
+ for (let i: number = 0; i < bytes; i++) {
3793
+ result = (result << 8n) | BigInt(buffer[offset + i]!);
3794
+ }
3795
+ } else {
3796
+ for (let i: number = bytes - 1; i >= 0; i--) {
3797
+ result = (result << 8n) | BigInt(buffer[offset + i]!);
3798
+ }
3799
+ }
3800
+
3801
+ if (signed) {
3802
+ const signBit: bigint = 1n << BigInt(bytes * 8 - 1);
3803
+ if (result & signBit) {
3804
+ result -= 1n << BigInt(bytes * 8);
3805
+ }
3806
+ }
3807
+
3808
+ return result;
3809
+ }
3810
+
3811
+ /**
3812
+ * Options for {@link writeSpecificIntType}.
3813
+ */
3814
+ export interface WriteSpecificIntTypeOptions {
3815
+ /**
3816
+ * Whether to wrap the value if it is out of range.
3817
+ *
3818
+ * If `false`, an error will be thrown if the value is out of range.
3819
+ *
3820
+ * @default false
3821
+ */
3822
+ wrap?: boolean;
3823
+ }
3824
+
3825
+ /**
3826
+ * Writes an integer to a buffer.
3827
+ *
3828
+ * @template TArrayBuffer The type of the buffer.
3829
+ * @param buffer The buffer to write to.
3830
+ * @param value The integer to write.
3831
+ * @param bytes The number of bytes to write.
3832
+ * @param format The endianness of the data.
3833
+ * @param signed The signedness of the data. Defaults to `false`.
3834
+ * @param offset The offset to write to. Defaults to `0`.
3835
+ * @param options The options to use.
3836
+ * @returns The buffer from the {@link buffer} parameter.
3837
+ *
3838
+ * @throws {RangeError} If the byte length is less than 1.
3839
+ * @throws {RangeError} If the buffer does not have enough space at the specified offset.
3840
+ * @throws {RangeError} If the value is out of range and {@link WriteSpecificIntTypeOptions.wrap | options.wrap} is `false`.
3841
+ */
3842
+ export function writeSpecificIntType<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>(
3843
+ buffer: Buffer<TArrayBuffer>,
3844
+ value: bigint,
3845
+ bytes: number,
3846
+ format: "BE" | "LE",
3847
+ signed: boolean = false,
3848
+ offset: number = 0,
3849
+ options?: WriteSpecificIntTypeOptions
3850
+ ): Buffer<TArrayBuffer> {
3851
+ if (bytes < 1) {
3852
+ throw new RangeError("Byte length must be at least 1");
3853
+ }
3854
+ if (offset + bytes > buffer.length) {
3855
+ throw new RangeError("Buffer does not have enough space at the specified offset");
3856
+ }
3857
+
3858
+ const bitSize: bigint = BigInt(bytes * 8);
3859
+ const maxUnsigned: bigint = (1n << bitSize) - 1n;
3860
+ const minSigned: bigint = -(1n << (bitSize - 1n));
3861
+ const maxSigned: bigint = (1n << (bitSize - 1n)) - 1n;
3862
+
3863
+ if (signed) {
3864
+ if (value < minSigned || value > maxSigned) {
3865
+ if (options?.wrap) {
3866
+ value = (value + (1n << bitSize)) % (1n << bitSize);
3867
+ } else {
3868
+ throw new RangeError(`Signed value out of range for ${bytes} bytes`);
3869
+ }
3870
+ }
3871
+ if (value < 0n) {
3872
+ value += 1n << bitSize;
3873
+ }
3874
+ } else {
3875
+ if (value < 0n || value > maxUnsigned) {
3876
+ if (options?.wrap) {
3877
+ value = value % (1n << bitSize);
3878
+ } else {
3879
+ throw new RangeError(`Unsigned value out of range for ${bytes} bytes`);
3880
+ }
3881
+ }
3882
+ }
3883
+
3884
+ for (let i: number = 0; i < bytes; i++) {
3885
+ const shift: bigint = format === "BE" ? BigInt((bytes - 1 - i) * 8) : BigInt(i * 8);
3886
+ buffer[offset + i] = Number((value >> shift) & 0xffn);
3887
+ }
3888
+
3889
+ return buffer;
3890
+ }
3891
+
3892
+ /**
3893
+ * Sanitizes a filename.
3894
+ *
3895
+ * @param filename The filename to sanitize.
3896
+ * @returns The sanitized filename.
3897
+ */
3898
+ export function sanitizeFilename(filename: string): string {
3899
+ return filename
3900
+ .replaceAll(fileNameCharacterFilterRegExp, /* (substring: string): string => encodeURIComponent(substring) */ "")
3901
+ .replaceAll(fileNameEncodeCharacterRegExp, (substring: string): string => encodeURIComponent(substring));
3902
+ }
3903
+
3904
+ /**
3905
+ * Sanitizes a display key.
3906
+ *
3907
+ * @param key The key to sanitize.
3908
+ * @returns The sanitized key.
3909
+ */
3910
+ export function sanitizeDisplayKey(key: string): string {
3911
+ return key.replaceAll(/[^a-zA-Z0-9-_+,-.;=@~/:>?\\]/g, (substring: string): string => encodeURIComponent(substring) /* "" */);
3912
+ }
3913
+
3914
+ /**
3915
+ * Converts a chunk block index to an offset from the minimum corner of the chunk.
3916
+ *
3917
+ * @param index The chunk block index.
3918
+ * @returns The offset from the minimum corner of the chunk.
3919
+ */
3920
+ export function chunkBlockIndexToOffset(index: number): Vector3 {
3921
+ return {
3922
+ x: (index >> 8) & 0xf,
3923
+ y: (index >> 0) & 0xf,
3924
+ z: (index >> 4) & 0xf,
3925
+ };
3926
+ }
3927
+
3928
+ /**
3929
+ * Converts an offset from the minimum corner of the chunk to a chunk block index.
3930
+ *
3931
+ * @param offset The offset from the minimum corner of the chunk.
3932
+ * @returns The chunk block index.
3933
+ */
3934
+ export function offsetToChunkBlockIndex(offset: Vector3): number {
3935
+ return ((offset.x & 0xf) << 8) | (offset.y & 0xf) | ((offset.z & 0xf) << 4);
3936
+ }
3937
+
3938
+ /**
3939
+ * Reads a 32-bit integer value from a Buffer at the given offset (little-endian).
3940
+ *
3941
+ * @param data The Buffer to read from.
3942
+ * @param offset The offset to read from.
3943
+ * @returns The 32-bit integer value.
3944
+ */
3945
+ export function getInt32Val(data: Buffer, offset: number): number {
3946
+ let retval: number = 0;
3947
+ // need to switch this to union based like the others.
3948
+ for (let i: number = 0; i < 4; i++) {
3949
+ // if I don't do the static cast, the top bit will be sign extended.
3950
+ retval |= data[offset + i]! << (i * 8);
3951
+ }
3952
+ return retval;
3953
+ }
3954
+
3955
+ /**
3956
+ * Writes a 32-bit integer value into a Buffer at the given offset (little-endian).
3957
+ *
3958
+ * @param buffer The Buffer to write into.
3959
+ * @param offset The offset to write into.
3960
+ * @param value The 32-bit integer value to write.
3961
+ */
3962
+ export function setInt32Val(buffer: Buffer, offset: number, value: number): void {
3963
+ for (let i: number = 0; i < 4; i++) {
3964
+ buffer[offset + i] = (value >> (i * 8)) & 0xff;
3965
+ }
3966
+ }
3967
+
3968
+ /**
3969
+ * Splits a range into smaller ranges of a given size.
3970
+ *
3971
+ * @param param0 The range to split.
3972
+ * @param size The size of each range.
3973
+ * @returns The split ranges.
3974
+ */
3975
+ export function splitRange([min, max]: [min: number, max: number], size: number): [from: number, to: number][] {
3976
+ const result: [from: number, to: number][] = [];
3977
+ let start: number = min;
3978
+
3979
+ while (start <= max) {
3980
+ const end: number = Math.min(start + size - 1, max);
3981
+ result.push([start, end]);
3982
+ start = end + 1;
3983
+ }
3984
+
3985
+ return result;
3986
+ }
3987
+
3988
+ /**
3989
+ * Packs block indices into the buffer using the same scheme as the read loop.
3990
+ *
3991
+ * @param buffer The buffer to write into.
3992
+ * @param blockDataOffset The offset where block data begins in the buffer.
3993
+ * @param block_indices The list of block indices to pack.
3994
+ * @param bitsPerBlock The number of bits used per block.
3995
+ * @param blocksPerWord How many blocks fit inside one 32-bit integer.
3996
+ */
3997
+ export function writeBlockIndices(buffer: Buffer, blockDataOffset: number, block_indices: number[], bitsPerBlock: number, blocksPerWord: number): void {
3998
+ const wordCount: number = Math.ceil(block_indices.length / blocksPerWord);
3999
+
4000
+ for (let wordIndex: number = 0; wordIndex < wordCount; wordIndex++) {
4001
+ let maskVal: number = 0;
4002
+
4003
+ for (let j: number = 0; j < blocksPerWord; j++) {
4004
+ const blockIndex: number = wordIndex * blocksPerWord + j;
4005
+ if (blockIndex >= block_indices.length) break;
4006
+
4007
+ const blockVal: number = block_indices[blockIndex]!;
4008
+ const shiftAmount: number = j * bitsPerBlock;
4009
+ maskVal |= blockVal /* & ((1 << bitsPerBlock) - 1) */ << shiftAmount;
4010
+ }
4011
+
4012
+ setInt32Val(buffer, blockDataOffset + wordIndex * 4, maskVal);
4013
+ }
4014
+ }
4015
+
4016
+ /**
4017
+ * Converts the dimension of a dimension vector to a numeric ID.
4018
+ *
4019
+ * @param dimension The dimension to convert.
4020
+ * @returns The numeric ID of the dimension.
4021
+ */
4022
+ export function dimensionVectorDimensionToInt(dimension: Dimension | number): number {
4023
+ return typeof dimension === "string" ? dimensions.indexOf(dimension) : dimension;
4024
+ }
4025
+
4026
+ /**
4027
+ * Converts the dimension of a dimension vector to a ID.
4028
+ *
4029
+ * Vanilla dimensions will not include a namespace.
4030
+ *
4031
+ * @param dimension The dimension to convert.
4032
+ * @param db The LevelDB to use.
4033
+ * @returns The ID of the dimension.
4034
+ *
4035
+ * @throws {ReferenceError} If the DimensionNameIdTable is not found.
4036
+ * @throws {ReferenceError} If the dimension is not found in the DimensionNameIdTable.
4037
+ */
4038
+ export async function dimensionVectorDimensionToId(dimension: Dimension | number, db: LevelDB): Promise<LooseAutocomplete<Dimension>> {
4039
+ if (typeof dimension === "string") return dimension;
4040
+ const rawDimensionNameIdTable: Buffer | null = await db.get("DimensionNameIdTable");
4041
+ if (rawDimensionNameIdTable === null) throw new ReferenceError("DimensionNameIdTable not found.");
4042
+ const dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name"> = NBT.parseUncompressed(
4043
+ rawDimensionNameIdTable,
4044
+ "little"
4045
+ ) as NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name">;
4046
+ const dimensionNamespacedId: string | undefined = Object.entries(dimensionNameIdTable.value.entries.value).find(
4047
+ ([_namespacedId, numericId]) => numericId.value === dimension
4048
+ )?.[0];
4049
+ if (dimensionNamespacedId === undefined) throw new ReferenceError(`Dimension ${dimension} not found in DimensionNameIdTable.`);
4050
+ return dimensionNamespacedId;
4051
+ }
4052
+
4053
+ /**
4054
+ * Converts the dimension of a dimension vector to a ID synchronously.
4055
+ *
4056
+ * Vanilla dimensions will not include a namespace.
4057
+ *
4058
+ * @param dimension The dimension to convert.
4059
+ * @param dimensionNameIdTable The DimensionNameIdTable to use.
4060
+ * @returns The ID of the dimension.
4061
+ *
4062
+ * @throws {ReferenceError} If the dimension is not found in the DimensionNameIdTable.
4063
+ */
4064
+ export function dimensionVectorDimensionToIdSync(
4065
+ dimension: Dimension | number,
4066
+ dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable
4067
+ ): LooseAutocomplete<Dimension> {
4068
+ if (typeof dimension === "string") return dimension;
4069
+ const dimensionNamespacedId: string | undefined = Object.entries(dimensionNameIdTable.value.entries.value).find(
4070
+ ([_namespacedId, numericId]) => numericId.value === dimension
4071
+ )?.[0];
4072
+ if (dimensionNamespacedId === undefined) throw new ReferenceError(`Dimension ${dimension} not found in DimensionNameIdTable.`);
4073
+ return dimensionNamespacedId;
4074
+ }
4075
+
4076
+ /**
4077
+ * Converts the ID of a dimension of a dimension vector.
4078
+ *
4079
+ * Vanilla dimensions do not require a namespace.
4080
+ *
4081
+ * @param dimension The dimension to convert. Case-sensitive.
4082
+ * @param db The LevelDB to use.
4083
+ * @returns The ID of the dimension.
4084
+ *
4085
+ * @throws {ReferenceError} If the DimensionNameIdTable is not found.
4086
+ * @throws {ReferenceError} If the dimension is not found in the DimensionNameIdTable.
4087
+ */
4088
+ export async function dimensionIdToDimensionVectorDimension(dimension: LooseAutocomplete<Dimension>, db: LevelDB): Promise<Dimension | number> {
4089
+ if (dimensions.includes(dimension.replace(/^minecraft:/, "") as Dimension)) return dimension.replace(/^minecraft:/, "") as Dimension;
4090
+ const rawDimensionNameIdTable: Buffer | null = await db.get("DimensionNameIdTable");
4091
+ if (rawDimensionNameIdTable === null) throw new ReferenceError("DimensionNameIdTable not found.");
4092
+ const dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name"> = NBT.parseUncompressed(
4093
+ rawDimensionNameIdTable,
4094
+ "little"
4095
+ ) as NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name">;
4096
+ const dimensionNumericId: number | undefined = Object.entries(dimensionNameIdTable.value.entries.value).find(
4097
+ ([namespacedId, _numericId]) => namespacedId === dimension
4098
+ )?.[1].value;
4099
+ if (dimensionNumericId === undefined) throw new ReferenceError(`Dimension ${dimension} not found in DimensionNameIdTable.`);
4100
+ return dimensionNumericId;
4101
+ }
4102
+
4103
+ /**
4104
+ * Converts the ID of a dimension of a dimension vector synchronously.
4105
+ *
4106
+ * Vanilla dimensions do not require a namespace.
4107
+ *
4108
+ * @param dimension The dimension to convert. Case-sensitive.
4109
+ * @param dimensionNameIdTable The DimensionNameIdTable to use.
4110
+ * @returns The ID of the dimension.
4111
+ *
4112
+ * @throws {ReferenceError} If the dimension is not found in the DimensionNameIdTable.
4113
+ */
4114
+ export function dimensionIdToDimensionVectorDimensionSync(
4115
+ dimension: LooseAutocomplete<Dimension>,
4116
+ dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable
4117
+ ): Dimension | number {
4118
+ if (dimensions.includes(dimension.replace(/^minecraft:/, "") as Dimension)) return dimension.replace(/^minecraft:/, "") as Dimension;
4119
+ const dimensionNumericId: number | undefined = Object.entries(dimensionNameIdTable.value.entries.value).find(
4120
+ ([namespacedId, _numericId]) => namespacedId === dimension
4121
+ )?.[1].value;
4122
+ if (dimensionNumericId === undefined) throw new ReferenceError(`Dimension ${dimension} not found in DimensionNameIdTable.`);
4123
+ return dimensionNumericId;
4124
+ }
4125
+
4126
+ /**
4127
+ * Converts a dimension's numeric ID to a dimension vector dimension.
4128
+ *
4129
+ * @param dimension The numeric ID of the dimension.
4130
+ * @returns The dimension vector dimension.
4131
+ */
4132
+ export function intToDimensionVectorDimension(dimension: number): Dimension | number {
4133
+ return dimensions[dimension] ?? dimension;
4134
+ }
4135
+
4136
+ /**
4137
+ * Gets the dimension types from the DimensionNameIdTable.
4138
+ *
4139
+ * @param db The LevelDB to use.
4140
+ * @returns The dimension types.
4141
+ *
4142
+ * @throws {ReferenceError} If the DimensionNameIdTable is not found.
4143
+ */
4144
+ export async function getDimensionTypes(db: LevelDB): Promise<Record<Dimension | `${string}:${string}`, number>> {
4145
+ const rawDimensionNameIdTable: Buffer | null = await db.get("DimensionNameIdTable");
4146
+ if (rawDimensionNameIdTable === null) throw new ReferenceError("DimensionNameIdTable not found.");
4147
+ const dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name"> = NBT.parseUncompressed(
4148
+ rawDimensionNameIdTable,
4149
+ "little"
4150
+ ) as NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name">;
4151
+ return getDimensionTypesSync(dimensionNameIdTable);
4152
+ }
4153
+
4154
+ /**
4155
+ * Gets the dimension types from the DimensionNameIdTable synchronously.
4156
+ *
4157
+ * @param dimensionNameIdTable The DimensionNameIdTable to use.
4158
+ * @returns The dimension types.
4159
+ */
4160
+ export function getDimensionTypesSync(dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable): Record<Dimension | `${string}:${string}`, number> {
4161
+ return {
4162
+ ...(Object.fromEntries(dimensions.map((dimension: Dimension, i: number) => [dimension, i])) as Record<Dimension, number>),
4163
+ ...(Object.fromEntries(
4164
+ Object.entries(dimensionNameIdTable.value.entries.value).map(([namespacedId, numericId]) => [namespacedId, numericId.value])
4165
+ ) as Record<`${string}:${string}` | Dimension, number>),
4166
+ };
4167
+ }
4168
+
4169
+ /**
4170
+ * Gets the dimension IDs from the DimensionNameIdTable.
4171
+ *
4172
+ * @param db The LevelDB to use.
4173
+ * @returns The dimension IDs.
4174
+ *
4175
+ * @throws {ReferenceError} If the DimensionNameIdTable is not found.
4176
+ */
4177
+ export async function getDimensionIds(db: LevelDB): Promise<(Dimension | `${string}:${string}`)[]> {
4178
+ const rawDimensionNameIdTable: Buffer | null = await db.get("DimensionNameIdTable");
4179
+ if (rawDimensionNameIdTable === null) throw new ReferenceError("DimensionNameIdTable not found.");
4180
+ const dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name"> = NBT.parseUncompressed(
4181
+ rawDimensionNameIdTable,
4182
+ "little"
4183
+ ) as NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name">;
4184
+ return getDimensionIdsSync(dimensionNameIdTable);
4185
+ }
4186
+
4187
+ /**
4188
+ * Gets the dimension IDs from the DimensionNameIdTable synchronously.
4189
+ *
4190
+ * @param dimensionNameIdTable The DimensionNameIdTable to use.
4191
+ * @returns The dimension IDs.
4192
+ */
4193
+ export function getDimensionIdsSync(dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable): (Dimension | `${string}:${string}`)[] {
4194
+ return [...new Set([...dimensions, ...(Object.keys(dimensionNameIdTable.value.entries.value) as `${string}:${string}`[])])];
4195
+ }
4196
+
4197
+ /**
4198
+ * Gets the dimension numeric IDs from the DimensionNameIdTable.
4199
+ *
4200
+ * @param db The LevelDB to use.
4201
+ * @returns The dimension numeric IDs.
4202
+ *
4203
+ * @throws {ReferenceError} If the DimensionNameIdTable is not found.
4204
+ */
4205
+ export async function getDimensionNumericIds(db: LevelDB): Promise<number[]> {
4206
+ const rawDimensionNameIdTable: Buffer | null = await db.get("DimensionNameIdTable");
4207
+ if (rawDimensionNameIdTable === null) throw new ReferenceError("DimensionNameIdTable not found.");
4208
+ const dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name"> = NBT.parseUncompressed(
4209
+ rawDimensionNameIdTable,
4210
+ "little"
4211
+ ) as NBTSchemas.NBTSchemaTypes.DimensionNameIdTable & Pick<NBT.NBT, "name">;
4212
+ return getDimensionNumericIdsSync(dimensionNameIdTable);
4213
+ }
4214
+
4215
+ /**
4216
+ * Gets the dimension numeric IDs from the DimensionNameIdTable synchronously.
4217
+ *
4218
+ * @param dimensionNameIdTable The DimensionNameIdTable to use.
4219
+ * @returns The dimension numeric IDs.
4220
+ */
4221
+ export function getDimensionNumericIdsSync(dimensionNameIdTable: NBTSchemas.NBTSchemaTypes.DimensionNameIdTable): number[] {
4222
+ return [
4223
+ ...new Set([
4224
+ ...dimensions.map((_dimension: Dimension, i: number): number => i),
4225
+ ...Object.values(dimensionNameIdTable.value.entries.value).map((numericId): number => numericId.value),
4226
+ ]),
4227
+ ];
4228
+ }
4229
+
4230
+ /**
4231
+ * Gets the chunk indices from a LevelDB key.
4232
+ *
4233
+ * The key must be a [chunk key](https://minecraft.wiki/w/Bedrock_Edition_level_format#Chunk_key_format).
4234
+ *
4235
+ * @param key The key to get the chunk indices for, as a Buffer.
4236
+ * @returns The chunk indices.
4237
+ */
4238
+ export function getChunkKeyIndices(key: Buffer): SubChunkIndexDimensionVectorXZ | DimensionVectorXZ {
4239
+ return {
4240
+ x: getInt32Val(key, 0),
4241
+ z: getInt32Val(key, 4),
4242
+ dimension: [13, 14].includes(key.length) ? (dimensions[getInt32Val(key, 8)] ?? getInt32Val(key, 8)) : "overworld",
4243
+ ...([10, 14].includes(key.length) ? { subChunkIndex: (key.at(-1)! << 24) >> 24 } : undefined),
4244
+ };
4245
+ }
4246
+
4247
+ /**
4248
+ * Generates a raw chunk key from chunk indices.
4249
+ *
4250
+ * @param indices The chunk indices.
4251
+ * @param chunkKeyType The chunk key type.
4252
+ * @returns The raw chunk key.
4253
+ */
4254
+ export function generateChunkKeyFromIndices(
4255
+ indices: SubChunkIndexDimensionVectorXZ | DimensionVectorXZ,
4256
+ chunkKeyType: DBChunkKeyEntryContentType
4257
+ ): Buffer<ArrayBuffer> {
4258
+ if (typeof indices.dimension === "string" && dimensions.indexOf(indices.dimension) === -1)
4259
+ throw new TypeError(`Invalid dimension: ${indices.dimension}. For custom dimensions please provide the numeric ID instead.`);
4260
+ const buffer: Buffer<ArrayBuffer> = Buffer.alloc(
4261
+ (indices.dimension === "overworld" ? 9 : 13) + +("subChunkIndex" in indices && indices.subChunkIndex !== undefined)
4262
+ );
4263
+ setInt32Val(buffer, 0, indices.x);
4264
+ setInt32Val(buffer, 4, indices.z);
4265
+ if (indices.dimension !== "overworld")
4266
+ setInt32Val(buffer, 8, typeof indices.dimension === "string" ? dimensions.indexOf(indices.dimension) : indices.dimension);
4267
+ buffer[8 + +(indices.dimension !== "overworld") * 4] = getIntFromChunkKeyType(chunkKeyType);
4268
+ if ("subChunkIndex" in indices && indices.subChunkIndex !== undefined) buffer[9 + +(indices.dimension !== "overworld") * 4] = indices.subChunkIndex;
4269
+ return buffer;
4270
+ }
4271
+
4272
+ /**
4273
+ * Converts a chunk key type to the integer value that represents it.
4274
+ *
4275
+ * @param chunkKeyType The chunk key type.
4276
+ * @returns The integer value.
4277
+ */
4278
+ function getIntFromChunkKeyType(chunkKeyType: DBChunkKeyEntryContentType): number {
4279
+ switch (chunkKeyType) {
4280
+ case "Data3D":
4281
+ return 0x2b;
4282
+ case "Version":
4283
+ return 0x2c;
4284
+ case "Data2D":
4285
+ return 0x2d;
4286
+ case "Data2DLegacy":
4287
+ return 0x2e;
4288
+ case "SubChunkPrefix":
4289
+ return 0x2f;
4290
+ case "LegacyTerrain":
4291
+ return 0x30;
4292
+ case "BlockEntity":
4293
+ return 0x31;
4294
+ case "Entity":
4295
+ return 0x32;
4296
+ case "PendingTicks":
4297
+ return 0x33;
4298
+ case "LegacyBlockExtraData":
4299
+ return 0x34;
4300
+ case "BiomeState":
4301
+ return 0x35;
4302
+ case "FinalizedState":
4303
+ return 0x36;
4304
+ case "ConversionData":
4305
+ return 0x37;
4306
+ case "BorderBlocks":
4307
+ return 0x38;
4308
+ case "HardcodedSpawners":
4309
+ return 0x39;
4310
+ case "RandomTicks":
4311
+ return 0x3a;
4312
+ case "Checksums":
4313
+ return 0x3b;
4314
+ case "GenerationSeed":
4315
+ return 0x3c;
4316
+ case "GeneratedPreCavesAndCliffsBlending":
4317
+ return 0x3d;
4318
+ case "BlendingBiomeHeight":
4319
+ return 0x3e;
4320
+ case "MetaDataHash":
4321
+ return 0x3f;
4322
+ case "BlendingData":
4323
+ return 0x40;
4324
+ case "ActorDigestVersion":
4325
+ return 0x41;
4326
+ case "LegacyVersion":
4327
+ return 0x76;
4328
+ case "AABBVolumes":
4329
+ return 0x77;
4330
+ }
4331
+ }
4332
+
4333
+ /**
4334
+ * Gets a human-readable version of a LevelDB key.
4335
+ *
4336
+ * @param key The key to get the display name for, as a Buffer.
4337
+ * @returns A human-readable version of the key.
4338
+ */
4339
+ export function getKeyDisplayName(key: Buffer): string {
4340
+ const contentType: DBEntryContentType = getContentTypeFromDBKey(key);
4341
+ switch (contentType) {
4342
+ case "Data3D":
4343
+ case "Version":
4344
+ case "Data2D":
4345
+ case "Data2DLegacy":
4346
+ case "SubChunkPrefix":
4347
+ case "LegacyTerrain":
4348
+ case "BlockEntity":
4349
+ case "Entity":
4350
+ case "PendingTicks":
4351
+ case "LegacyBlockExtraData":
4352
+ case "BiomeState":
4353
+ case "FinalizedState":
4354
+ case "ConversionData":
4355
+ case "BorderBlocks":
4356
+ case "HardcodedSpawners":
4357
+ case "RandomTicks":
4358
+ case "Checksums":
4359
+ case "GenerationSeed":
4360
+ case "GeneratedPreCavesAndCliffsBlending":
4361
+ case "BlendingBiomeHeight":
4362
+ case "MetaDataHash":
4363
+ case "BlendingData":
4364
+ case "ActorDigestVersion":
4365
+ case "LegacyVersion":
4366
+ case "AABBVolumes": {
4367
+ const indices: SubChunkIndexDimensionVectorXZ | DimensionVectorXZ = getChunkKeyIndices(key);
4368
+ return `${indices.dimension}_${indices.x}_${indices.z}${
4369
+ "subChunkIndex" in indices && indices.subChunkIndex !== undefined ? `_${indices.subChunkIndex}` : ""
4370
+ }_${contentType}`;
4371
+ }
4372
+ case "Digest": {
4373
+ const indices: DimensionVectorXZ = getChunkKeyIndices(key.subarray(4));
4374
+ return `digp_${indices.dimension}_${indices.x}_${indices.z}`;
4375
+ }
4376
+ case "ActorPrefix": {
4377
+ return `actorprefix_${getInt32Val(key, key.length - 8)}_${getInt32Val(key, key.length - 4)}`;
4378
+ }
4379
+ case "AutonomousEntities":
4380
+ case "BiomeData":
4381
+ case "BiomeIdsTable":
4382
+ case "ChunkLoadedRequest":
4383
+ case "DimensionNameIdTable":
4384
+ case "Overworld":
4385
+ case "Nether":
4386
+ case "TheEnd":
4387
+ case "CustomDimension":
4388
+ case "DynamicProperties":
4389
+ case "FlatWorldLayers":
4390
+ case "ForcedWorldCorruption":
4391
+ case "LegacyOverworld":
4392
+ case "LegacyNether":
4393
+ case "LegacyTheEnd":
4394
+ case "LevelChunkMetaDataDictionary":
4395
+ case "LevelDat":
4396
+ case "LevelSpawnWasFixed":
4397
+ case "PositionTrackingDB":
4398
+ case "PositionTrackingLastId":
4399
+ case "Map":
4400
+ case "MobEvents":
4401
+ case "MVillages":
4402
+ case "Player":
4403
+ case "PlayerClient":
4404
+ case "Portals":
4405
+ case "RealmsStoriesData":
4406
+ case "SchedulerWT":
4407
+ case "Scoreboard":
4408
+ case "StructureTemplate":
4409
+ case "TickingArea":
4410
+ case "VillageDwellers":
4411
+ case "VillageInfo":
4412
+ case "VillagePOI":
4413
+ case "VillagePlayers":
4414
+ case "VillageRaid":
4415
+ case "Villages":
4416
+ case "WorldClocks":
4417
+ case "Unknown":
4418
+ default:
4419
+ return key.toString("binary");
4420
+ }
4421
+ }
4422
+
4423
+ /**
4424
+ * Gets the content type of a LevelDB key.
4425
+ *
4426
+ * @param key The key to get the content type for, as a Buffer.
4427
+ * @returns The content type of the key.
4428
+ */
4429
+ export function getContentTypeFromDBKey(key: Buffer): DBEntryContentType {
4430
+ if ([9, 10, 13, 14].includes(key.length)) {
4431
+ switch (key.at([10, 14].includes(key.length) ? -2 : -1)) {
4432
+ case 0x2b:
4433
+ return "Data3D";
4434
+ case 0x2c:
4435
+ return "Version";
4436
+ case 0x2d:
4437
+ return "Data2D";
4438
+ case 0x2e:
4439
+ return "Data2DLegacy";
4440
+ case 0x2f:
4441
+ return "SubChunkPrefix";
4442
+ case 0x30:
4443
+ return "LegacyTerrain";
4444
+ case 0x31:
4445
+ return "BlockEntity";
4446
+ case 0x32:
4447
+ return "Entity";
4448
+ case 0x33:
4449
+ return "PendingTicks";
4450
+ case 0x34:
4451
+ return "LegacyBlockExtraData";
4452
+ case 0x35:
4453
+ return "BiomeState";
4454
+ case 0x36:
4455
+ return "FinalizedState";
4456
+ case 0x37:
4457
+ return "ConversionData";
4458
+ case 0x38:
4459
+ return "BorderBlocks";
4460
+ case 0x39:
4461
+ return "HardcodedSpawners";
4462
+ case 0x3a:
4463
+ return "RandomTicks";
4464
+ case 0x3b:
4465
+ return "Checksums";
4466
+ case 0x3c:
4467
+ return "GenerationSeed";
4468
+ case 0x3d:
4469
+ return "GeneratedPreCavesAndCliffsBlending";
4470
+ case 0x3e:
4471
+ return "BlendingBiomeHeight";
4472
+ case 0x3f:
4473
+ return "MetaDataHash";
4474
+ case 0x40:
4475
+ return "BlendingData";
4476
+ case 0x41:
4477
+ return "ActorDigestVersion";
4478
+ case 0x76:
4479
+ return "LegacyVersion";
4480
+ case 0x77:
4481
+ return "AABBVolumes";
4482
+ }
4483
+ }
4484
+ const stringKey: string = key.toString();
4485
+ switch (stringKey) {
4486
+ case "~local_player":
4487
+ return "Player";
4488
+ case "game_flatworldlayers":
4489
+ return "FlatWorldLayers";
4490
+ case "Overworld":
4491
+ return "Overworld";
4492
+ case "Nether":
4493
+ return "Nether";
4494
+ case "TheEnd":
4495
+ return "TheEnd";
4496
+ case "mobevents":
4497
+ return "MobEvents";
4498
+ case "BiomeData":
4499
+ return "BiomeData";
4500
+ case "BiomeIdsTable":
4501
+ return "BiomeIdsTable";
4502
+ case "DimensionNameIdTable":
4503
+ return "DimensionNameIdTable";
4504
+ case "AutonomousEntities":
4505
+ return "AutonomousEntities";
4506
+ case "PositionTrackDB-LastId":
4507
+ return "PositionTrackingLastId";
4508
+ case "scoreboard":
4509
+ return "Scoreboard";
4510
+ case "schedulerWT":
4511
+ return "SchedulerWT";
4512
+ case "portals":
4513
+ return "Portals";
4514
+ case "DynamicProperties":
4515
+ return "DynamicProperties";
4516
+ case "LevelChunkMetaDataDictionary":
4517
+ return "LevelChunkMetaDataDictionary";
4518
+ case "WorldClocks":
4519
+ return "WorldClocks";
4520
+ case "SST_SALOG":
4521
+ case "SST_WORD":
4522
+ case "SST_WORD_":
4523
+ case "DedicatedServerForcedCorruption":
4524
+ return "ForcedWorldCorruption";
4525
+ case "dimension0":
4526
+ return "LegacyOverworld";
4527
+ case "dimension1":
4528
+ return "LegacyNether";
4529
+ case "dimension2":
4530
+ return "LegacyTheEnd";
4531
+ case "mVillages":
4532
+ return "MVillages";
4533
+ case "villages":
4534
+ return "Villages";
4535
+ case "LevelSpawnWasFixed":
4536
+ return "LevelSpawnWasFixed";
4537
+ }
4538
+ switch (true) {
4539
+ case stringKey.startsWith("PosTrackDB-0x"):
4540
+ return "PositionTrackingDB";
4541
+ case stringKey.startsWith("actorprefix"):
4542
+ return "ActorPrefix";
4543
+ case stringKey.startsWith("structuretemplate_"):
4544
+ return "StructureTemplate";
4545
+ case stringKey.startsWith("tickingarea"):
4546
+ return "TickingArea";
4547
+ case stringKey.startsWith("portals"):
4548
+ return "Portals";
4549
+ case stringKey.startsWith("map_"):
4550
+ return "Map";
4551
+ case stringKey.startsWith("player_server_"):
4552
+ return "Player";
4553
+ case stringKey.startsWith("player_"):
4554
+ return "PlayerClient";
4555
+ case stringKey.startsWith("digp"):
4556
+ return "Digest";
4557
+ case /^chunk_loaded_request_(?:[Oo][Vv][Ee][Rr][Ww][Oo][Rr][Ll][Dd]|[Tt][Hh][Ee]_[Ee][Nn][Dd]|[Nn][Ee][Tt][Hh][Ee][Rr])_\d+$/.test(stringKey):
4558
+ return "ChunkLoadedRequest";
4559
+ case stringKey.startsWith("RealmsStoriesData_"):
4560
+ return "RealmsStoriesData";
4561
+ 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):
4562
+ return "VillagePOI";
4563
+ 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):
4564
+ return "VillageInfo";
4565
+ 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):
4566
+ return "VillageDwellers";
4567
+ 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):
4568
+ return "VillagePlayers";
4569
+ 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\\-]+_RAID$/.test(stringKey):
4570
+ return "VillageRaid";
4571
+ case /^[\u0001-\u0008\u000b-\u000c\u000e-\u001f\u0021-\u0039\u003b-\uffff]+:[\u0001-\u0008\u000b-\u000c\u000e-\u001f\u0021-\u0039\u003b-\uffff]+$/.test(
4572
+ stringKey
4573
+ ):
4574
+ return "CustomDimension";
4575
+ }
4576
+ return "Unknown";
4577
+ }
4578
+
4579
+ /**
4580
+ * Gets the biome type from its ID.
4581
+ *
4582
+ * Only works with vanilla biomes.
4583
+ *
4584
+ * @param id The ID of the biome.
4585
+ * @returns The biome type.
4586
+ */
4587
+ export function getBiomeTypeFromID(id: number): keyof (typeof BiomeData)["int_map"] | undefined {
4588
+ return (Object.keys(BiomeData.int_map) as (keyof (typeof BiomeData)["int_map"])[]).find(
4589
+ (key: keyof (typeof BiomeData)["int_map"]): boolean => BiomeData.int_map[key] === id
4590
+ );
4591
+ }
4592
+
4593
+ /**
4594
+ * Gets the biome ID from its type.
4595
+ *
4596
+ * Only works with vanilla biomes.
4597
+ *
4598
+ * @param type The type of the biome.
4599
+ * @returns The biome ID.
4600
+ */
4601
+ export function getBiomeIDFromType(type: keyof (typeof BiomeData)["int_map"]): number | undefined {
4602
+ return BiomeData.int_map[type];
4603
+ }
4604
+
4605
+ //#endregion
4606
+
4607
+ // --------------------------------------------------------------------------------
4608
+ // Classes
4609
+ // --------------------------------------------------------------------------------
4610
+
4611
+ //#region Classes
4612
+
4613
+ /**
4614
+ * @todo
4615
+ */
4616
+ export class Structure {
4617
+ /**
4618
+ * @todo
4619
+ */
4620
+ public target?:
4621
+ | {
4622
+ /**
4623
+ * The type of the target structure data.
4624
+ */
4625
+ type: "LevelDBEntry";
4626
+ /**
4627
+ * The LevelDB containing the target structure data.
4628
+ */
4629
+ db: LevelDB;
4630
+ /**
4631
+ * The key of the target structure data in the LevelDB.
4632
+ */
4633
+ key: Buffer;
4634
+ }
4635
+ | {
4636
+ /**
4637
+ * The type of the target structure data.
4638
+ */
4639
+ type: "File";
4640
+ /**
4641
+ * The absolute path to the target structure data.
4642
+ */
4643
+ path: string;
4644
+ }
4645
+ | undefined;
4646
+ /**
4647
+ * @todo
4648
+ */
4649
+ public constructor(options: { target?: Structure["target"] }) {
4650
+ this.target = options.target;
4651
+ }
4652
+ /**
4653
+ * @todo
4654
+ */
4655
+ public fillBlocks(from: Vector3, to: Vector3, block: NBTSchemas.NBTSchemaTypes.Block): any {
4656
+ throw new Error("Method not implemented.");
4657
+ }
4658
+ /**
4659
+ * @todo
4660
+ */
4661
+ public saveChanges(): any {
4662
+ throw new Error("Method not implemented.");
4663
+ }
4664
+ /**
4665
+ * @todo
4666
+ */
4667
+ public delete(): any {
4668
+ throw new Error("Method not implemented.");
4669
+ }
4670
+ /**
4671
+ * @todo
4672
+ */
4673
+ public expand(min: Vector3, max: Vector3): any {
4674
+ throw new Error("Method not implemented.");
4675
+ }
4676
+ /**
4677
+ * @todo
4678
+ */
4679
+ public shrink(min: Vector3, max: Vector3): any {
4680
+ throw new Error("Method not implemented.");
4681
+ }
4682
+ /**
4683
+ * @todo
4684
+ */
4685
+ public move(min: Vector3, max: Vector3): any {
4686
+ throw new Error("Method not implemented.");
4687
+ }
4688
+ /**
4689
+ * @todo
4690
+ */
4691
+ public scale(scale: Vector3 | number): any {
4692
+ throw new Error("Method not implemented.");
4693
+ }
4694
+ /**
4695
+ * @todo
4696
+ */
4697
+ public rotate(angle: 0 | 90 | 180 | 270, axis: "x" | "y" | "z" = "y"): any {
4698
+ throw new Error("Method not implemented.");
4699
+ }
4700
+ /**
4701
+ * @todo
4702
+ */
4703
+ public mirror(axis: "x" | "y" | "z"): any {
4704
+ throw new Error("Method not implemented.");
4705
+ }
4706
+ /**
4707
+ * @todo
4708
+ */
4709
+ public clear(): any {
4710
+ throw new Error("Method not implemented.");
4711
+ }
4712
+ /**
4713
+ * @todo
4714
+ */
4715
+ public clearSectionData(from: Vector3, to: Vector3): any {
4716
+ throw new Error("Method not implemented.");
4717
+ }
4718
+ /**
4719
+ * @todo
4720
+ */
4721
+ public getSectionData(from: Vector3, to: Vector3): StructureSectionData {
4722
+ throw new Error("Method not implemented.");
4723
+ }
4724
+ /**
4725
+ * @todo
4726
+ */
4727
+ public replaceSectionData(offset: Vector3, data: StructureSectionData, options: StructureReplaceSectionDataOptions = {}): any {
4728
+ throw new Error("Method not implemented.");
4729
+ }
4730
+ /**
4731
+ * @todo
4732
+ */
4733
+ public exportPrismarineNBT(): NBTSchemas.NBTSchemaTypes.StructureTemplate {
4734
+ throw new Error("Method not implemented.");
4735
+ }
4736
+ }
4737
+
4738
+ /**
4739
+ * Options for {@link Structure.replaceSectionData}.
4740
+ */
4741
+ export interface StructureReplaceSectionDataOptions {
4742
+ /**
4743
+ * Whether to automatically expand the structure to fit the data if necessary, instead of cropping it.
4744
+ *
4745
+ * @default false
4746
+ */
4747
+ autoExpand?: boolean;
4748
+ }
4749
+
4750
+ //#endregion