mcbe-leveldb 1.6.0-jsonly → 1.7.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/Changelog.md +7 -1
- package/DBUtils.js +4 -4
- package/DBUtils.js.map +1 -1
- package/DBUtils.ts +184 -0
- package/LevelUtils.d.ts +58 -3
- package/LevelUtils.js +60 -8
- package/LevelUtils.js.map +1 -1
- package/LevelUtils.ts +2716 -0
- package/SNBTUtils.ts +2132 -0
- package/__biome_data__.ts +282 -0
- package/index.d.ts +2 -2
- package/index.ts +14 -0
- package/nbtSchemas.ts +18662 -0
- package/package.json +6 -2
- package/utils/JSONB.ts +566 -0
- package/trialSpawnerLocations.json +0 -20
package/LevelUtils.ts
ADDED
|
@@ -0,0 +1,2716 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
import NBT from "prismarine-nbt";
|
|
3
|
+
import BiomeData from "./__biome_data__.ts";
|
|
4
|
+
import type { LevelDB } from "@8crafter/leveldb-zlib";
|
|
5
|
+
import type { NBTSchemas } from "./nbtSchemas.ts";
|
|
6
|
+
|
|
7
|
+
//#region Local Constants
|
|
8
|
+
|
|
9
|
+
const DEBUG = false;
|
|
10
|
+
const fileNameEncodeCharacterRegExp = /[/:>?\\]/g;
|
|
11
|
+
const fileNameCharacterFilterRegExp = /[^a-zA-Z0-9-_+,-.;=@~/:>?\\]/g;
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
|
|
15
|
+
//#region Local Functions
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A tuple of length 16.
|
|
19
|
+
*
|
|
20
|
+
* @template T The type of the elements in the tuple.
|
|
21
|
+
*/
|
|
22
|
+
type TupleOfLength16<T> = [T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T];
|
|
23
|
+
|
|
24
|
+
function readInt16LE(buf: Uint8Array, offset: number): number {
|
|
25
|
+
const val = buf[offset]! | (buf[offset + 1]! << 8);
|
|
26
|
+
return val >= 0x8000 ? val - 0x10000 : val;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeInt16LE(value: number): Buffer<ArrayBuffer> {
|
|
30
|
+
const buf = Buffer.alloc(2);
|
|
31
|
+
buf.writeInt16LE(value, 0);
|
|
32
|
+
return buf;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeInt32LE(value: number): Buffer<ArrayBuffer> {
|
|
36
|
+
const buf = Buffer.alloc(4);
|
|
37
|
+
buf.writeInt32LE(value, 0);
|
|
38
|
+
return buf;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// export function readData3dValue(rawvalue: Uint8Array | null): {
|
|
42
|
+
// /**
|
|
43
|
+
// * A height map in the form [x][z].
|
|
44
|
+
// */
|
|
45
|
+
// heightMap: TupleOfLength16<TupleOfLength16<number>>;
|
|
46
|
+
// /**
|
|
47
|
+
// * A biome map in the form [x][y][z].
|
|
48
|
+
// */
|
|
49
|
+
// biomeMap: TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>>;
|
|
50
|
+
// } | null {
|
|
51
|
+
// if (!rawvalue) {
|
|
52
|
+
// return null;
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// // --- Height map (first 512 bytes -> 256 signed 16-bit ints) ---
|
|
56
|
+
// const heightMap: TupleOfLength16<TupleOfLength16<number>> = Array.from(
|
|
57
|
+
// { length: 16 },
|
|
58
|
+
// (): TupleOfLength16<number> => Array(16).fill(0) as TupleOfLength16<number>
|
|
59
|
+
// ) as TupleOfLength16<TupleOfLength16<number>>;
|
|
60
|
+
// for (let i: number = 0; i < 256; i++) {
|
|
61
|
+
// const val: number = readInt16LE(rawvalue, i * 2);
|
|
62
|
+
// const x: number = i % 16;
|
|
63
|
+
// const z: number = Math.floor(i / 16);
|
|
64
|
+
// heightMap[x]![z] = val;
|
|
65
|
+
// }
|
|
66
|
+
|
|
67
|
+
// // --- Biome subchunks (remaining bytes) ---
|
|
68
|
+
// const biomeBytes: Uint8Array<ArrayBuffer> = rawvalue.slice(512);
|
|
69
|
+
// let b: BiomePalette[] = readChunkBiomes(biomeBytes);
|
|
70
|
+
|
|
71
|
+
// // Validate Biome Data
|
|
72
|
+
// if (b.length === 0 || b[0]!.values === null) {
|
|
73
|
+
// throw new Error("Value does not contain at least one subchunk of biome data.");
|
|
74
|
+
// }
|
|
75
|
+
|
|
76
|
+
// // Enlarge list to length 24 if necessary
|
|
77
|
+
// if (b.length < 24) {
|
|
78
|
+
// while (b.length < 24) b.push({ values: null, palette: [] });
|
|
79
|
+
// }
|
|
80
|
+
|
|
81
|
+
// // Trim biome data
|
|
82
|
+
// if (b.length > 24) {
|
|
83
|
+
// if (b.slice(24).some((sub: BiomePalette): boolean => sub.values !== null)) {
|
|
84
|
+
// console.warn(`Trimming biome data from ${b.length} to 24 subchunks.`);
|
|
85
|
+
// }
|
|
86
|
+
// b = b.slice(0, 24);
|
|
87
|
+
// }
|
|
88
|
+
|
|
89
|
+
// // --- Fill biome_map [16][24*16][16] (x,y,z order) ---
|
|
90
|
+
// const biomeMap: TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>> = Array.from(
|
|
91
|
+
// { length: 16 },
|
|
92
|
+
// (): TupleOfLength16<TupleOfLength16<number>> =>
|
|
93
|
+
// Array.from({ length: 24 * 16 }, (): TupleOfLength16<number> => Array(16).fill(NaN) as TupleOfLength16<number>) as TupleOfLength16<
|
|
94
|
+
// TupleOfLength16<number>
|
|
95
|
+
// >
|
|
96
|
+
// ) as TupleOfLength16<TupleOfLength16<TupleOfLength16<number>>>;
|
|
97
|
+
|
|
98
|
+
// const hasData: boolean[] = b.map((sub: BiomePalette): boolean => sub.values !== null);
|
|
99
|
+
// const ii: number[] = hasData
|
|
100
|
+
// .map((has: boolean, idx: number): number | null => (has ? idx + 1 : null))
|
|
101
|
+
// .filter((i: number | null): i is number => i !== null);
|
|
102
|
+
|
|
103
|
+
// for (const i of ii) {
|
|
104
|
+
// const bb: BiomePalette = b[i - 1]!;
|
|
105
|
+
// if (!bb.values) continue;
|
|
106
|
+
|
|
107
|
+
// for (let u: number = 0; u < 4096; u++) {
|
|
108
|
+
// const val: number = bb.palette[bb.values[u]! - 1]!; // R is 1-based
|
|
109
|
+
// const x: number = u % 16;
|
|
110
|
+
// const z: number = Math.floor(u / 16) % 16;
|
|
111
|
+
// const y: number = Math.floor(u / 256);
|
|
112
|
+
// biomeMap[x]![16 * (i - 1) + y]![z] = val;
|
|
113
|
+
// }
|
|
114
|
+
// }
|
|
115
|
+
|
|
116
|
+
// // Fill missing subchunks by copying top of last data subchunk
|
|
117
|
+
// const iMax: number = Math.max(...ii);
|
|
118
|
+
// if (iMax < 24) {
|
|
119
|
+
// const y: number = 16 * iMax - 1;
|
|
120
|
+
// for (let yy: number = y + 1; yy < 24 * 16; yy++) {
|
|
121
|
+
// for (let x: number = 0; x < 16; x++) {
|
|
122
|
+
// for (let z: number = 0; z < 16; z++) {
|
|
123
|
+
// biomeMap[x]![yy]![z] = biomeMap[x]![y]![z]!;
|
|
124
|
+
// }
|
|
125
|
+
// }
|
|
126
|
+
// }
|
|
127
|
+
// }
|
|
128
|
+
|
|
129
|
+
// return { heightMap, biomeMap };
|
|
130
|
+
// }
|
|
131
|
+
/**
|
|
132
|
+
* Reads the value of the Data3D content type.
|
|
133
|
+
*
|
|
134
|
+
* @param rawvalue The raw value to read.
|
|
135
|
+
* @returns The height map and biome map.
|
|
136
|
+
*/
|
|
137
|
+
export function readData3dValue(rawvalue: Uint8Array | null): {
|
|
138
|
+
/**
|
|
139
|
+
* A height map in the form [x][z].
|
|
140
|
+
*/
|
|
141
|
+
heightMap: TupleOfLength16<TupleOfLength16<number>>;
|
|
142
|
+
/**
|
|
143
|
+
* A biome map as an array of 24 or more BiomePalette objects.
|
|
144
|
+
*/
|
|
145
|
+
biomes: BiomePalette[];
|
|
146
|
+
} | null {
|
|
147
|
+
if (!rawvalue) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Height map (first 512 bytes -> 256 signed 16-bit ints) ---
|
|
152
|
+
const heightMap: TupleOfLength16<TupleOfLength16<number>> = Array.from(
|
|
153
|
+
{ length: 16 },
|
|
154
|
+
(): TupleOfLength16<number> => Array(16).fill(0) as TupleOfLength16<number>
|
|
155
|
+
) as TupleOfLength16<TupleOfLength16<number>>;
|
|
156
|
+
|
|
157
|
+
for (let i: number = 0; i < 256; i++) {
|
|
158
|
+
const val: number = readInt16LE(rawvalue, i * 2);
|
|
159
|
+
const x: number = i % 16;
|
|
160
|
+
const z: number = Math.floor(i / 16);
|
|
161
|
+
heightMap[x]![z] = val;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// --- Biome subchunks (remaining bytes) ---
|
|
165
|
+
const biomeBytes: Uint8Array = rawvalue.slice(512);
|
|
166
|
+
let biomes: BiomePalette[] = readChunkBiomes(biomeBytes);
|
|
167
|
+
|
|
168
|
+
// Validate Biome Data
|
|
169
|
+
if (biomes.length === 0 || biomes[0]!.values === null) {
|
|
170
|
+
throw new Error("Value does not contain at least one subchunk of biome data.");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Enlarge list to length 24 if necessary
|
|
174
|
+
if (biomes.length < 24) {
|
|
175
|
+
while (biomes.length < 24) {
|
|
176
|
+
biomes.push({ values: null, palette: [] });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Trim biome data (DISABLED (so that when increasing the height limits, it still works properly))
|
|
181
|
+
// if (biomeMap.length > 24) {
|
|
182
|
+
// if (biomeMap.slice(24).some((sub: BiomePalette): boolean => sub.values !== null)) {
|
|
183
|
+
// console.warn(`Trimming biome data from ${biomeMap.length} to 24 subchunks.`);
|
|
184
|
+
// }
|
|
185
|
+
// biomeMap = biomeMap.slice(0, 24);
|
|
186
|
+
// }
|
|
187
|
+
|
|
188
|
+
return { heightMap, biomes };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Writes the value of the Data3D content type.
|
|
193
|
+
*
|
|
194
|
+
* @param heightMap A height map in the form [x][z].
|
|
195
|
+
* @param biomes A biome map as an array of 24 or more BiomePalette objects.
|
|
196
|
+
* @returns The raw value to write.
|
|
197
|
+
*/
|
|
198
|
+
export function writeData3DValue(heightMap: TupleOfLength16<TupleOfLength16<number>>, biomes: BiomePalette[]): Buffer<ArrayBuffer> {
|
|
199
|
+
// heightMap is 16x16, flatten to 256 shorts
|
|
200
|
+
const flatHeight: number[] = heightMap.flatMap((v: TupleOfLength16<number>, i: number): number[] =>
|
|
201
|
+
v.map((_v2: number, i2: number): number => heightMap[i2]![i]!)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Write height map (512 bytes)
|
|
205
|
+
const heightBufs: Buffer[] = flatHeight.map((v: number): Buffer<ArrayBuffer> => writeInt16LE(v));
|
|
206
|
+
const heightBuf: Buffer<ArrayBuffer> = Buffer.concat(heightBufs);
|
|
207
|
+
|
|
208
|
+
// Write biome data
|
|
209
|
+
const biomeBuf: Buffer<ArrayBuffer> = writeChunkBiomes(biomes);
|
|
210
|
+
|
|
211
|
+
// Combine
|
|
212
|
+
return Buffer.concat([heightBuf, biomeBuf]);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Reads chunk biome data from a buffer.
|
|
217
|
+
*
|
|
218
|
+
* @param rawValue The raw value to read.
|
|
219
|
+
* @returns The biome data.
|
|
220
|
+
*
|
|
221
|
+
* @internal
|
|
222
|
+
*/
|
|
223
|
+
function readChunkBiomes(rawValue: Uint8Array): BiomePalette[] {
|
|
224
|
+
let p: number = 0;
|
|
225
|
+
const end: number = rawValue.length;
|
|
226
|
+
const result: BiomePalette[] = [];
|
|
227
|
+
|
|
228
|
+
while (p < end) {
|
|
229
|
+
const { values, isPersistent, paletteSize, newOffset } = readSubchunkPaletteIds(rawValue, p, end);
|
|
230
|
+
p = newOffset;
|
|
231
|
+
|
|
232
|
+
if (values === null) {
|
|
233
|
+
result.push({ values: null, palette: [] });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (isPersistent) {
|
|
238
|
+
throw new Error("Biome palette does not have runtime ids.", { cause: result.length });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (end - p < paletteSize * 4) {
|
|
242
|
+
throw new Error("Subchunk biome palette is truncated.");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const palette: number[] = [];
|
|
246
|
+
for (let i: number = 0; i < paletteSize; i++) {
|
|
247
|
+
const val: number = rawValue[p]! | (rawValue[p + 1]! << 8) | (rawValue[p + 2]! << 16) | (rawValue[p + 3]! << 24);
|
|
248
|
+
palette.push(val);
|
|
249
|
+
p += 4;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
result.push({ values, palette });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Writes the chunk biome data from a BiomePalette array to a buffer.
|
|
260
|
+
*
|
|
261
|
+
* @param biomes The biome data to write.
|
|
262
|
+
* @returns The resulting buffer.
|
|
263
|
+
*
|
|
264
|
+
* @internal
|
|
265
|
+
*/
|
|
266
|
+
function writeChunkBiomes(biomes: BiomePalette[]): Buffer<ArrayBuffer> {
|
|
267
|
+
const buffers: Buffer<ArrayBuffer>[] = [];
|
|
268
|
+
|
|
269
|
+
for (const bb of biomes) {
|
|
270
|
+
if (!bb || bb.values === null || bb.values.length === 0) continue; // NULL case in R
|
|
271
|
+
const { values, palette } = bb;
|
|
272
|
+
|
|
273
|
+
// --- Write subchunk palette ids (bitpacked) ---
|
|
274
|
+
const idsBuf: Buffer<ArrayBuffer> = writeSubchunkPaletteIds(values!, palette.length);
|
|
275
|
+
|
|
276
|
+
// --- Write palette size ---
|
|
277
|
+
const paletteSizeBuf: Buffer<ArrayBuffer> = writeInt32LE(palette.length);
|
|
278
|
+
|
|
279
|
+
// --- Write palette values ---
|
|
280
|
+
const paletteBufs: Buffer<ArrayBuffer>[] = palette.map((v: number): Buffer<ArrayBuffer> => writeInt32LE(v));
|
|
281
|
+
|
|
282
|
+
buffers.push(idsBuf, paletteSizeBuf, ...paletteBufs);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return Buffer.concat(buffers);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function readSubchunkPaletteIds(
|
|
289
|
+
buffer: Uint8Array,
|
|
290
|
+
offset: number,
|
|
291
|
+
end: number
|
|
292
|
+
): {
|
|
293
|
+
values: number[] | null;
|
|
294
|
+
isPersistent: boolean;
|
|
295
|
+
paletteSize: number;
|
|
296
|
+
newOffset: number;
|
|
297
|
+
} {
|
|
298
|
+
let p = offset;
|
|
299
|
+
|
|
300
|
+
if (end - p < 1) {
|
|
301
|
+
throw new Error("Subchunk biome error: not enough data for flags.");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const flags = buffer[p++]!;
|
|
305
|
+
const isPersistent = (flags & 1) === 0;
|
|
306
|
+
const bitsPerBlock = flags >> 1;
|
|
307
|
+
|
|
308
|
+
// Special case: palette copy
|
|
309
|
+
if (bitsPerBlock === 127) {
|
|
310
|
+
return { values: null, isPersistent, paletteSize: 0, newOffset: p };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const values = new Array(4096);
|
|
314
|
+
|
|
315
|
+
if (bitsPerBlock > 0) {
|
|
316
|
+
const blocksPerWord = Math.floor(32 / bitsPerBlock);
|
|
317
|
+
const wordCount = Math.floor(4095 / blocksPerWord) + 1;
|
|
318
|
+
const mask = (1 << bitsPerBlock) - 1;
|
|
319
|
+
|
|
320
|
+
if (end - p < 4 * wordCount) {
|
|
321
|
+
throw new Error("Subchunk biome error: not enough data for block words.");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let u = 0;
|
|
325
|
+
for (let j = 0; j < wordCount; j++) {
|
|
326
|
+
let temp = buffer[p]! | (buffer[p + 1]! << 8) | (buffer[p + 2]! << 16) | (buffer[p + 3]! << 24);
|
|
327
|
+
p += 4;
|
|
328
|
+
|
|
329
|
+
for (let k = 0; k < blocksPerWord && u < 4096; k++) {
|
|
330
|
+
// const x = (u >> 8) & 0xf;
|
|
331
|
+
// const z = (u >> 4) & 0xf;
|
|
332
|
+
// const y = u & 0xf;
|
|
333
|
+
|
|
334
|
+
values[u] = temp & mask;
|
|
335
|
+
temp >>= bitsPerBlock;
|
|
336
|
+
u++;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (end - p < 4) {
|
|
341
|
+
throw new Error("Subchunk biome error: missing palette size.");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const paletteSize = buffer[p]! | (buffer[p + 1]! << 8) | (buffer[p + 2]! << 16) | (buffer[p + 3]! << 24);
|
|
345
|
+
p += 4;
|
|
346
|
+
|
|
347
|
+
return { values, isPersistent, paletteSize, newOffset: p };
|
|
348
|
+
} else {
|
|
349
|
+
// bitsPerBlock == 0 -> everything is ID 1
|
|
350
|
+
for (let u = 0; u < 4096; u++) {
|
|
351
|
+
values[u] = 0;
|
|
352
|
+
}
|
|
353
|
+
return { values, isPersistent, paletteSize: 1, newOffset: p };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function writeSubchunkPaletteIds(values: number[], paletteSize: number): Buffer<ArrayBuffer> {
|
|
358
|
+
const blockCount: number = values.length; // usually 16*16*16 = 4096
|
|
359
|
+
const bitsPerBlock: number = Math.max(1, Math.ceil(Math.log2(paletteSize)));
|
|
360
|
+
const wordsPerBlock: number = Math.ceil((blockCount * bitsPerBlock) / 32);
|
|
361
|
+
const words = new Uint32Array(wordsPerBlock);
|
|
362
|
+
|
|
363
|
+
// let bitIndex: number = 0;
|
|
364
|
+
// for (const v of values) {
|
|
365
|
+
// let idx: number = v >>> 0; // ensure unsigned
|
|
366
|
+
// for (let i: number = 0; i < bitsPerBlock; i++) {
|
|
367
|
+
// if (idx & (1 << i)) {
|
|
368
|
+
// const wordIndex: number = Math.floor(bitIndex / 32);
|
|
369
|
+
// const bitOffset: number = bitIndex % 32;
|
|
370
|
+
// words[wordIndex]! |= 1 << bitOffset;
|
|
371
|
+
// }
|
|
372
|
+
// bitIndex++;
|
|
373
|
+
// }
|
|
374
|
+
// }
|
|
375
|
+
let bitIndex = 0;
|
|
376
|
+
for (let i = 0; i < values.length; i++) {
|
|
377
|
+
let val = values[i]! & ((1 << bitsPerBlock) - 1); // mask to bpe bits
|
|
378
|
+
const wordIndex = Math.floor(bitIndex / 32);
|
|
379
|
+
const bitOffset = bitIndex % 32;
|
|
380
|
+
words[wordIndex]! |= val << bitOffset;
|
|
381
|
+
if (bitOffset + bitsPerBlock > 32) {
|
|
382
|
+
// spill over into next word
|
|
383
|
+
words[wordIndex + 1]! |= val >>> (32 - bitOffset);
|
|
384
|
+
}
|
|
385
|
+
bitIndex += bitsPerBlock;
|
|
386
|
+
}
|
|
387
|
+
// words.forEach((val, i) => {
|
|
388
|
+
// words[i] = (-val - 1) & 0xffffffff;
|
|
389
|
+
// });
|
|
390
|
+
|
|
391
|
+
// --- Write header byte ---
|
|
392
|
+
const header: Buffer<ArrayBuffer> = Buffer.alloc(1);
|
|
393
|
+
header.writeUInt8((bitsPerBlock << 1) | 1, 0);
|
|
394
|
+
|
|
395
|
+
// --- Write packed data ---
|
|
396
|
+
const packed: Buffer<ArrayBuffer> = Buffer.alloc(words.length * 4);
|
|
397
|
+
for (let i: number = 0; i < words.length; i++) {
|
|
398
|
+
packed.writeUInt32LE(words[i]!, i * 4);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return Buffer.concat([header, packed]);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
//#endregion
|
|
405
|
+
|
|
406
|
+
// --------------------------------------------------------------------------------
|
|
407
|
+
// Constants
|
|
408
|
+
// --------------------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
//#region Constants
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* The list of Minecraft dimensions.
|
|
414
|
+
*/
|
|
415
|
+
export const dimensions = ["overworld", "nether", "the_end"] as const;
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* The list of Minecraft game modes, with their indices corresponding to their numeric gamemode IDs.
|
|
419
|
+
*/
|
|
420
|
+
export const gameModes = ["survival", "creative", "adventure", , , "default", "spectator"] as const;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The content types for LevelDB entries.
|
|
424
|
+
*/
|
|
425
|
+
export const DBEntryContentTypes = [
|
|
426
|
+
// Biome Linked
|
|
427
|
+
"Data3D",
|
|
428
|
+
"Version",
|
|
429
|
+
"Data2D",
|
|
430
|
+
"Data2DLegacy",
|
|
431
|
+
"SubChunkPrefix",
|
|
432
|
+
"LegacyTerrain",
|
|
433
|
+
"BlockEntity",
|
|
434
|
+
"Entity",
|
|
435
|
+
"PendingTicks",
|
|
436
|
+
"LegacyBlockExtraData",
|
|
437
|
+
"BiomeState",
|
|
438
|
+
"FinalizedState",
|
|
439
|
+
"ConversionData",
|
|
440
|
+
"BorderBlocks",
|
|
441
|
+
"HardcodedSpawners",
|
|
442
|
+
"RandomTicks",
|
|
443
|
+
"Checksums",
|
|
444
|
+
"MetaDataHash",
|
|
445
|
+
"GeneratedPreCavesAndCliffsBlending",
|
|
446
|
+
"BlendingBiomeHeight",
|
|
447
|
+
"BlendingData",
|
|
448
|
+
"ActorDigestVersion",
|
|
449
|
+
"LegacyVersion",
|
|
450
|
+
"AABBVolumes", // TO-DO: Figure out how to parse this, it seems to have data for trial chambers and trail ruins.
|
|
451
|
+
// Village
|
|
452
|
+
"VillageDwellers",
|
|
453
|
+
"VillageInfo",
|
|
454
|
+
"VillagePOI",
|
|
455
|
+
"VillagePlayers",
|
|
456
|
+
// Standalone
|
|
457
|
+
"Player",
|
|
458
|
+
"PlayerClient",
|
|
459
|
+
"ActorPrefix",
|
|
460
|
+
"ChunkLoadedRequest",
|
|
461
|
+
"Digest",
|
|
462
|
+
"Map",
|
|
463
|
+
"Portals",
|
|
464
|
+
"SchedulerWT",
|
|
465
|
+
"StructureTemplate",
|
|
466
|
+
"TickingArea",
|
|
467
|
+
"FlatWorldLayers",
|
|
468
|
+
"Scoreboard",
|
|
469
|
+
"Dimension",
|
|
470
|
+
"AutonomousEntities",
|
|
471
|
+
"BiomeData",
|
|
472
|
+
"MobEvents",
|
|
473
|
+
"DynamicProperties",
|
|
474
|
+
"LevelChunkMetaDataDictionary",
|
|
475
|
+
"RealmsStoriesData",
|
|
476
|
+
"LevelDat",
|
|
477
|
+
// Dev Version
|
|
478
|
+
"ForcedWorldCorruption",
|
|
479
|
+
// Misc.
|
|
480
|
+
"Unknown",
|
|
481
|
+
] as const;
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* The content type to format mapping for LevelDB entries.
|
|
485
|
+
*/
|
|
486
|
+
export const entryContentTypeToFormatMap = {
|
|
487
|
+
/**
|
|
488
|
+
* The Data3D content type contains heightmap and biome data for 16x16x16 chunks of the world.
|
|
489
|
+
*
|
|
490
|
+
* @see {@link NBTSchemas.nbtSchemas.Data3D}
|
|
491
|
+
*/
|
|
492
|
+
Data3D: {
|
|
493
|
+
/**
|
|
494
|
+
* The format type of the data.
|
|
495
|
+
*/
|
|
496
|
+
type: "custom",
|
|
497
|
+
/**
|
|
498
|
+
* The format type that results from the {@link entryContentTypeToFormatMap.Data3D.parse | parse} method.
|
|
499
|
+
*/
|
|
500
|
+
resultType: "JSONNBT",
|
|
501
|
+
/**
|
|
502
|
+
* The function to parse the data.
|
|
503
|
+
*
|
|
504
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
505
|
+
*
|
|
506
|
+
* @param data The data to parse, as a buffer.
|
|
507
|
+
* @returns The parsed data.
|
|
508
|
+
*
|
|
509
|
+
* @throws {Error} If the value does not contain at least one subchunk of biome data.
|
|
510
|
+
*/
|
|
511
|
+
parse(data: Buffer): NBTSchemas.NBTSchemaTypes.Data3D {
|
|
512
|
+
const result = readData3dValue(data);
|
|
513
|
+
return {
|
|
514
|
+
type: "compound",
|
|
515
|
+
value: {
|
|
516
|
+
heightMap: {
|
|
517
|
+
type: "list",
|
|
518
|
+
value: {
|
|
519
|
+
type: "list",
|
|
520
|
+
value: result!.heightMap.map((row) => ({
|
|
521
|
+
type: "short",
|
|
522
|
+
value: row,
|
|
523
|
+
})),
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
biomes: {
|
|
527
|
+
type: "list",
|
|
528
|
+
value: {
|
|
529
|
+
type: "compound",
|
|
530
|
+
value: result!.biomes.map(
|
|
531
|
+
(subchunk: BiomePalette): NBTSchemas.NBTSchemaTypes.Data3D["value"]["biomes"]["value"]["value"][number] => ({
|
|
532
|
+
values: {
|
|
533
|
+
type: "list",
|
|
534
|
+
value:
|
|
535
|
+
subchunk.values ?
|
|
536
|
+
{
|
|
537
|
+
type: "int",
|
|
538
|
+
value: subchunk.values,
|
|
539
|
+
}
|
|
540
|
+
: ({
|
|
541
|
+
type: "end",
|
|
542
|
+
value: [],
|
|
543
|
+
} as any),
|
|
544
|
+
},
|
|
545
|
+
palette: {
|
|
546
|
+
type: "list",
|
|
547
|
+
value: {
|
|
548
|
+
type: "int",
|
|
549
|
+
value: subchunk.palette,
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
})
|
|
553
|
+
),
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
},
|
|
559
|
+
/**
|
|
560
|
+
* The function to serialize the data.
|
|
561
|
+
*
|
|
562
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
563
|
+
*
|
|
564
|
+
* @param data The data to serialize.
|
|
565
|
+
* @returns The serialized data, as a buffer.
|
|
566
|
+
*/
|
|
567
|
+
serialize(data: NBTSchemas.NBTSchemaTypes.Data3D): Buffer<ArrayBuffer> {
|
|
568
|
+
return writeData3DValue(
|
|
569
|
+
data.value.heightMap.value.value.map((row: { type: "short"; value: number[] }): number[] => row.value) as any,
|
|
570
|
+
data.value.biomes.value.value.map(
|
|
571
|
+
(subchunk: NBTSchemas.NBTSchemaTypes.Data3D["value"]["biomes"]["value"]["value"][number]): BiomePalette => ({
|
|
572
|
+
palette: subchunk.palette.value.value,
|
|
573
|
+
values: !subchunk.values.value.value?.length ? null : subchunk.values.value.value,
|
|
574
|
+
})
|
|
575
|
+
)
|
|
576
|
+
);
|
|
577
|
+
},
|
|
578
|
+
// TO-DO: Add a default value for this.
|
|
579
|
+
},
|
|
580
|
+
/**
|
|
581
|
+
* The version of a chunk.
|
|
582
|
+
*
|
|
583
|
+
* The current chunk version as of `v1.21.111` is `41` (`0x29`).
|
|
584
|
+
*
|
|
585
|
+
* Deleting think key causes the game to completely regenerate the corresponding chunk.
|
|
586
|
+
*/
|
|
587
|
+
Version: {
|
|
588
|
+
/**
|
|
589
|
+
* The format type of the data.
|
|
590
|
+
*/
|
|
591
|
+
type: "int",
|
|
592
|
+
/**
|
|
593
|
+
* How many bytes this integer is.
|
|
594
|
+
*/
|
|
595
|
+
bytes: 1,
|
|
596
|
+
/**
|
|
597
|
+
* The endianness of the data.
|
|
598
|
+
*/
|
|
599
|
+
format: "LE",
|
|
600
|
+
/**
|
|
601
|
+
* The signedness of the data.
|
|
602
|
+
*/
|
|
603
|
+
signed: false,
|
|
604
|
+
/**
|
|
605
|
+
* The default value to use when initializing a new entry.
|
|
606
|
+
*
|
|
607
|
+
* Bytes:
|
|
608
|
+
* ```json
|
|
609
|
+
* [41]
|
|
610
|
+
* ```
|
|
611
|
+
*/
|
|
612
|
+
defaultValue: Buffer.from([41]),
|
|
613
|
+
},
|
|
614
|
+
/**
|
|
615
|
+
* @deprecated Only used in versions < 1.18.0.
|
|
616
|
+
*
|
|
617
|
+
* @todo Make a parser for this so that versions < 1.18.0 can be supported.
|
|
618
|
+
* @todo Add a description for this.
|
|
619
|
+
*/
|
|
620
|
+
Data2D: {
|
|
621
|
+
/**
|
|
622
|
+
* The format type of the data.
|
|
623
|
+
*/
|
|
624
|
+
type: "unknown",
|
|
625
|
+
},
|
|
626
|
+
/**
|
|
627
|
+
* @deprecated Only used in versions < 1.0.0.
|
|
628
|
+
*
|
|
629
|
+
* @todo Make a parser for this so that versions < 1.0.0 can be supported.
|
|
630
|
+
* @todo Add a description for this.
|
|
631
|
+
*/
|
|
632
|
+
Data2DLegacy: {
|
|
633
|
+
/**
|
|
634
|
+
* The format type of the data.
|
|
635
|
+
*/
|
|
636
|
+
type: "unknown",
|
|
637
|
+
},
|
|
638
|
+
/**
|
|
639
|
+
* The SubChunkPrefix content type contains block data for 16x16x16 chunks of the world.
|
|
640
|
+
*
|
|
641
|
+
* @see {@link NBTSchemas.nbtSchemas.SubChunkPrefix}
|
|
642
|
+
*/
|
|
643
|
+
SubChunkPrefix: {
|
|
644
|
+
/**
|
|
645
|
+
* The format type of the data.
|
|
646
|
+
*/
|
|
647
|
+
type: "custom",
|
|
648
|
+
/**
|
|
649
|
+
* The format type that results from the {@link entryContentTypeToFormatMap.SubChunkPrefix.parse | parse} method.
|
|
650
|
+
*/
|
|
651
|
+
resultType: "JSONNBT",
|
|
652
|
+
/**
|
|
653
|
+
* The function to parse the data.
|
|
654
|
+
*
|
|
655
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
656
|
+
*
|
|
657
|
+
* @param data The data to parse, as a buffer.
|
|
658
|
+
* @returns A promise that resolves with the parsed data.
|
|
659
|
+
*
|
|
660
|
+
* @throws {Error} If the SubChunkPrefix version is unknown.
|
|
661
|
+
*/
|
|
662
|
+
async parse(data: Buffer): Promise<NBTSchemas.NBTSchemaTypes.SubChunkPrefix> {
|
|
663
|
+
let currentOffset: number = 0;
|
|
664
|
+
const layers: NBTSchemas.NBTSchemaTypes.SubChunkPrefixLayer["value"][] = [];
|
|
665
|
+
/**
|
|
666
|
+
* The version of the SubChunkPrefix.
|
|
667
|
+
*
|
|
668
|
+
* Should be `0x08` (1.2.13 <= x < 1.18.0) or `0x09` (1.18.0 <= x).
|
|
669
|
+
*/
|
|
670
|
+
const version: 0x08 | 0x09 = data[currentOffset++]! as any;
|
|
671
|
+
if (![0x08, 0x09].includes(version)) throw new Error(`Unknown SubChunkPrefix version: ${version}`);
|
|
672
|
+
/**
|
|
673
|
+
* How many blocks are in each location (ex. 2 might mean here is a waterlog layer).
|
|
674
|
+
*/
|
|
675
|
+
let numStorageBlocks: number = data[currentOffset++]!;
|
|
676
|
+
const subChunkIndex: number | undefined = version >= 0x09 ? data[currentOffset++] : undefined;
|
|
677
|
+
for (let blockIndex: number = 0; blockIndex < numStorageBlocks; blockIndex++) {
|
|
678
|
+
const storageVersion: number = data[currentOffset]!;
|
|
679
|
+
currentOffset++;
|
|
680
|
+
const bitsPerBlock: number = storageVersion >> 1;
|
|
681
|
+
const blocksPerWord: number = Math.floor(32 / bitsPerBlock);
|
|
682
|
+
const numints: number = Math.ceil(4096 / blocksPerWord);
|
|
683
|
+
const blockDataOffset: number = currentOffset;
|
|
684
|
+
let paletteOffset: number = blockDataOffset + 4 * numints;
|
|
685
|
+
let psize: number = bitsPerBlock === 0 ? 0 : getInt32Val(data, paletteOffset);
|
|
686
|
+
paletteOffset += Math.sign(bitsPerBlock) * 4;
|
|
687
|
+
// const debugInfo = {
|
|
688
|
+
// version,
|
|
689
|
+
// blockIndex,
|
|
690
|
+
// storageVersion,
|
|
691
|
+
// bitsPerBlock,
|
|
692
|
+
// blocksPerWord,
|
|
693
|
+
// numints,
|
|
694
|
+
// blockDataOffset,
|
|
695
|
+
// paletteOffset,
|
|
696
|
+
// psize,
|
|
697
|
+
// // blockData,
|
|
698
|
+
// // palette,
|
|
699
|
+
// };
|
|
700
|
+
const palette: {
|
|
701
|
+
[paletteIndex: `${bigint}`]: NBTSchemas.NBTSchemaTypes.Block;
|
|
702
|
+
} = {};
|
|
703
|
+
for (let i: bigint = 0n; i < psize; i++) {
|
|
704
|
+
// console.log(debugInfo);
|
|
705
|
+
// appendFileSync("./test1.bin", JSON.stringify(debugInfo) + "\n");
|
|
706
|
+
const result = (await NBT.parse(data.subarray(paletteOffset), "little")) as unknown as {
|
|
707
|
+
parsed: NBTSchemas.NBTSchemaTypes.Block;
|
|
708
|
+
type: NBT.NBTFormat;
|
|
709
|
+
metadata: NBT.Metadata;
|
|
710
|
+
};
|
|
711
|
+
paletteOffset += result.metadata.size;
|
|
712
|
+
palette[`${i}`] = result.parsed;
|
|
713
|
+
// console.log(result);
|
|
714
|
+
// appendFileSync("./test1.bin", JSON.stringify(result));
|
|
715
|
+
}
|
|
716
|
+
currentOffset = paletteOffset;
|
|
717
|
+
const block_indices: number[] = [];
|
|
718
|
+
for (let i: number = 0; i < 4096; i++) {
|
|
719
|
+
const maskVal: number = getInt32Val(data, blockDataOffset + Math.floor(i / blocksPerWord) * 4);
|
|
720
|
+
const blockVal: number = (maskVal >> ((i % blocksPerWord) * bitsPerBlock)) & ((1 << bitsPerBlock) - 1);
|
|
721
|
+
// const blockType: DataTypes_Block | undefined = palette[`${blockVal as unknown as bigint}`];
|
|
722
|
+
// if (!blockType && blockVal !== -1) throw new ReferenceError(`Invalid block palette index ${blockVal} for block ${i}`);
|
|
723
|
+
/* const chunkOffset: Vector3 = {
|
|
724
|
+
x: (i >> 8) & 0xf,
|
|
725
|
+
y: (i >> 4) & 0xf,
|
|
726
|
+
z: (i >> 0) & 0xf,
|
|
727
|
+
}; */
|
|
728
|
+
block_indices.push(blockVal);
|
|
729
|
+
}
|
|
730
|
+
layers.push({
|
|
731
|
+
storageVersion: NBT.byte(storageVersion),
|
|
732
|
+
palette: NBT.comp(palette),
|
|
733
|
+
block_indices: { type: "list", value: { type: "int", value: block_indices } },
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
return NBT.comp({
|
|
737
|
+
version: NBT.byte<0x08 | 0x09>(version),
|
|
738
|
+
layerCount: NBT.byte(numStorageBlocks),
|
|
739
|
+
layers: { type: "list", value: NBT.comp(layers) },
|
|
740
|
+
...(version >= 0x09 ?
|
|
741
|
+
{
|
|
742
|
+
subChunkIndex: NBT.byte(subChunkIndex!),
|
|
743
|
+
}
|
|
744
|
+
: {}),
|
|
745
|
+
} as const satisfies NBTSchemas.NBTSchemaTypes.SubChunkPrefix["value"]);
|
|
746
|
+
},
|
|
747
|
+
/**
|
|
748
|
+
* The function to serialize the data.
|
|
749
|
+
*
|
|
750
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
751
|
+
*
|
|
752
|
+
* @param data The data to serialize.
|
|
753
|
+
* @returns The serialized data, as a buffer.
|
|
754
|
+
*/
|
|
755
|
+
serialize(data: NBTSchemas.NBTSchemaTypes.SubChunkPrefix): Buffer<ArrayBuffer> {
|
|
756
|
+
const buffer: Buffer<ArrayBuffer> = Buffer.from([
|
|
757
|
+
data.value.version.value,
|
|
758
|
+
data.value.layerCount.value,
|
|
759
|
+
...(data.value.version.value >= 0x09 ? [data.value.subChunkIndex!.value] : []),
|
|
760
|
+
]);
|
|
761
|
+
const layerBuffers: Buffer<ArrayBuffer>[] = data.value.layers.value.value.map(
|
|
762
|
+
(layer: NBTSchemas.NBTSchemaTypes.SubChunkPrefixLayer["value"]): Buffer<ArrayBuffer> => {
|
|
763
|
+
const bitsPerBlock: number = layer.storageVersion.value >> 1;
|
|
764
|
+
const blocksPerWord: number = Math.floor(32 / bitsPerBlock);
|
|
765
|
+
const numints: number = Math.ceil(4096 / blocksPerWord);
|
|
766
|
+
const bytes: number[] = [layer.storageVersion.value];
|
|
767
|
+
const blockIndicesBuffer: Buffer<ArrayBuffer> = Buffer.alloc(Math.ceil(numints * 4));
|
|
768
|
+
writeBlockIndices(blockIndicesBuffer, 0, layer.block_indices.value.value, bitsPerBlock, blocksPerWord);
|
|
769
|
+
bytes.push(...blockIndicesBuffer);
|
|
770
|
+
const paletteLengthBuffer: Buffer<ArrayBuffer> = Buffer.alloc(4);
|
|
771
|
+
setInt32Val(paletteLengthBuffer, 0, Object.keys(layer.palette.value).length);
|
|
772
|
+
bytes.push(...paletteLengthBuffer);
|
|
773
|
+
const paletteKeys: `${bigint}`[] = (Object.keys(layer.palette.value) as `${bigint}`[]).sort(
|
|
774
|
+
(a: `${bigint}`, b: `${bigint}`): number => Number(a) - Number(b)
|
|
775
|
+
);
|
|
776
|
+
for (let paletteIndex: number = 0; paletteIndex < paletteKeys.length; paletteIndex++) {
|
|
777
|
+
const block: NBTSchemas.NBTSchemaTypes.Block = layer.palette.value[paletteKeys[paletteIndex]!]!;
|
|
778
|
+
bytes.push(...NBT.writeUncompressed({ name: "", ...block }, "little"));
|
|
779
|
+
}
|
|
780
|
+
return Buffer.from(bytes);
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
return Buffer.concat([buffer, ...layerBuffers]);
|
|
784
|
+
},
|
|
785
|
+
// TO-DO: Add a default value for this.
|
|
786
|
+
},
|
|
787
|
+
/**
|
|
788
|
+
* @deprecated Only used in versions < 1.0.0.
|
|
789
|
+
*
|
|
790
|
+
* @todo Make a parser for this so that versions < 1.0.0 can be supported.
|
|
791
|
+
* @todo Add a description for this.
|
|
792
|
+
*/
|
|
793
|
+
LegacyTerrain: {
|
|
794
|
+
/**
|
|
795
|
+
* The format type of the data.
|
|
796
|
+
*/
|
|
797
|
+
type: "unknown",
|
|
798
|
+
},
|
|
799
|
+
/**
|
|
800
|
+
* A list of block entities associated with a chunk.
|
|
801
|
+
*
|
|
802
|
+
* @see {@link NBTSchemas.nbtSchemas.BlockEntity}
|
|
803
|
+
*/
|
|
804
|
+
BlockEntity: {
|
|
805
|
+
/**
|
|
806
|
+
* The format type of the data.
|
|
807
|
+
*/
|
|
808
|
+
type: "custom",
|
|
809
|
+
/**
|
|
810
|
+
* The format type that results from the {@link entryContentTypeToFormatMap.BlockEntity.parse | parse} method.
|
|
811
|
+
*/
|
|
812
|
+
resultType: "JSONNBT",
|
|
813
|
+
/**
|
|
814
|
+
* The function to parse the data.
|
|
815
|
+
*
|
|
816
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
817
|
+
*
|
|
818
|
+
* @param data The data to parse, as a buffer.
|
|
819
|
+
* @returns A promise that resolves with the parsed data.
|
|
820
|
+
*
|
|
821
|
+
* @throws {any} If an error occurs while parsing the data.
|
|
822
|
+
*/
|
|
823
|
+
async parse(data: Buffer): Promise<{
|
|
824
|
+
type: "compound";
|
|
825
|
+
value: {
|
|
826
|
+
blockEntities: {
|
|
827
|
+
type: "list";
|
|
828
|
+
value: { type: "compound"; value: NBTSchemas.NBTSchemaTypes.BlockEntity["value"][] };
|
|
829
|
+
};
|
|
830
|
+
};
|
|
831
|
+
}> {
|
|
832
|
+
const blockEntities: NBTSchemas.NBTSchemaTypes.BlockEntity["value"][] = [];
|
|
833
|
+
let currentIndex: number = 0;
|
|
834
|
+
while (currentIndex < data.length) {
|
|
835
|
+
const result = await NBT.parse(data.subarray(currentIndex), "little");
|
|
836
|
+
currentIndex += result.metadata.size;
|
|
837
|
+
blockEntities.push(result.parsed.value as NBTSchemas.NBTSchemaTypes.BlockEntity["value"]);
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
type: "compound",
|
|
841
|
+
value: {
|
|
842
|
+
blockEntities: {
|
|
843
|
+
type: "list",
|
|
844
|
+
value: {
|
|
845
|
+
type: "compound",
|
|
846
|
+
value: blockEntities,
|
|
847
|
+
},
|
|
848
|
+
},
|
|
849
|
+
},
|
|
850
|
+
};
|
|
851
|
+
},
|
|
852
|
+
/**
|
|
853
|
+
* The function to serialize the data.
|
|
854
|
+
*
|
|
855
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
856
|
+
*
|
|
857
|
+
* @param data The data to serialize.
|
|
858
|
+
* @returns The serialized data, as a buffer.
|
|
859
|
+
*/
|
|
860
|
+
serialize(data: {
|
|
861
|
+
type: "compound";
|
|
862
|
+
value: {
|
|
863
|
+
blockEntities: {
|
|
864
|
+
type: "list";
|
|
865
|
+
value: { type: "compound"; value: NBTSchemas.NBTSchemaTypes.BlockEntity["value"][] };
|
|
866
|
+
};
|
|
867
|
+
};
|
|
868
|
+
}): Buffer<ArrayBuffer> {
|
|
869
|
+
const nbtData: Buffer[] = data.value.blockEntities.value.value.map(
|
|
870
|
+
(blockEntity: NBTSchemas.NBTSchemaTypes.BlockEntity["value"]): Buffer =>
|
|
871
|
+
NBT.writeUncompressed({ name: "", type: "compound", value: blockEntity }, "little")
|
|
872
|
+
);
|
|
873
|
+
return Buffer.concat(nbtData);
|
|
874
|
+
},
|
|
875
|
+
},
|
|
876
|
+
/**
|
|
877
|
+
* @deprecated No longer used.
|
|
878
|
+
*
|
|
879
|
+
* @todo Figure out what version this was deprecated in (it exists in v1.16.40 but not in 1.21.51).
|
|
880
|
+
* @todo Add a description for this.
|
|
881
|
+
*/
|
|
882
|
+
Entity: {
|
|
883
|
+
/**
|
|
884
|
+
* The format type of the data.
|
|
885
|
+
*/
|
|
886
|
+
type: "custom",
|
|
887
|
+
/**
|
|
888
|
+
* The format type that results from the {@link entryContentTypeToFormatMap.Entity.parse | parse} method.
|
|
889
|
+
*/
|
|
890
|
+
resultType: "JSONNBT",
|
|
891
|
+
/**
|
|
892
|
+
* The function to parse the data.
|
|
893
|
+
*
|
|
894
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
895
|
+
*
|
|
896
|
+
* @param data The data to parse, as a buffer.
|
|
897
|
+
* @returns A promise that resolves with the parsed data.
|
|
898
|
+
*
|
|
899
|
+
* @throws {any} If an error occurs while parsing the data.
|
|
900
|
+
*/
|
|
901
|
+
async parse(data: Buffer): Promise<{
|
|
902
|
+
type: "compound";
|
|
903
|
+
value: {
|
|
904
|
+
entities: {
|
|
905
|
+
type: "list";
|
|
906
|
+
value: { type: "compound"; value: NBTSchemas.NBTSchemaTypes.ActorPrefix["value"][] };
|
|
907
|
+
};
|
|
908
|
+
};
|
|
909
|
+
}> {
|
|
910
|
+
const entities: NBTSchemas.NBTSchemaTypes.ActorPrefix["value"][] = [];
|
|
911
|
+
let currentIndex: number = 0;
|
|
912
|
+
while (currentIndex < data.length) {
|
|
913
|
+
const result = await NBT.parse(data.subarray(currentIndex), "little");
|
|
914
|
+
currentIndex += result.metadata.size;
|
|
915
|
+
entities.push(result.parsed.value as NBTSchemas.NBTSchemaTypes.ActorPrefix["value"]);
|
|
916
|
+
}
|
|
917
|
+
return {
|
|
918
|
+
type: "compound",
|
|
919
|
+
value: {
|
|
920
|
+
entities: {
|
|
921
|
+
type: "list",
|
|
922
|
+
value: {
|
|
923
|
+
type: "compound",
|
|
924
|
+
value: entities,
|
|
925
|
+
},
|
|
926
|
+
},
|
|
927
|
+
},
|
|
928
|
+
};
|
|
929
|
+
},
|
|
930
|
+
/**
|
|
931
|
+
* The function to serialize the data.
|
|
932
|
+
*
|
|
933
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
934
|
+
*
|
|
935
|
+
* @param data The data to serialize.
|
|
936
|
+
* @returns The serialized data, as a buffer.
|
|
937
|
+
*/
|
|
938
|
+
serialize(data: {
|
|
939
|
+
type: "compound";
|
|
940
|
+
value: {
|
|
941
|
+
entities: {
|
|
942
|
+
type: "list";
|
|
943
|
+
value: { type: "compound"; value: NBTSchemas.NBTSchemaTypes.ActorPrefix["value"][] };
|
|
944
|
+
};
|
|
945
|
+
};
|
|
946
|
+
}): Buffer<ArrayBuffer> {
|
|
947
|
+
const nbtData: Buffer[] = data.value.entities.value.value.map(
|
|
948
|
+
(entity: NBTSchemas.NBTSchemaTypes.ActorPrefix["value"]): Buffer =>
|
|
949
|
+
NBT.writeUncompressed({ name: "", type: "compound", value: entity }, "little")
|
|
950
|
+
);
|
|
951
|
+
return Buffer.concat(nbtData);
|
|
952
|
+
},
|
|
953
|
+
},
|
|
954
|
+
/**
|
|
955
|
+
* @see {@link NBTSchemas.nbtSchemas.PendingTicks}
|
|
956
|
+
*
|
|
957
|
+
* @todo Add a description for this.
|
|
958
|
+
*/
|
|
959
|
+
PendingTicks: {
|
|
960
|
+
/**
|
|
961
|
+
* The format type of the data.
|
|
962
|
+
*/
|
|
963
|
+
type: "NBT",
|
|
964
|
+
// TO-DO: Add a default value for this.
|
|
965
|
+
},
|
|
966
|
+
/**
|
|
967
|
+
* @deprecated Only used in versions < 1.2.3.
|
|
968
|
+
*
|
|
969
|
+
* @todo Figure out how to parse this.
|
|
970
|
+
* @todo Add a description for this.
|
|
971
|
+
*/
|
|
972
|
+
LegacyBlockExtraData: {
|
|
973
|
+
/**
|
|
974
|
+
* The format type of the data.
|
|
975
|
+
*/
|
|
976
|
+
type: "unknown",
|
|
977
|
+
},
|
|
978
|
+
/**
|
|
979
|
+
* @todo Figure out how to parse this.
|
|
980
|
+
* @todo Add a description for this.
|
|
981
|
+
*/
|
|
982
|
+
BiomeState: {
|
|
983
|
+
/**
|
|
984
|
+
* The format type of the data.
|
|
985
|
+
*/
|
|
986
|
+
type: "unknown",
|
|
987
|
+
},
|
|
988
|
+
/**
|
|
989
|
+
* A value that indicates the finalization state of a chunk's data.
|
|
990
|
+
*
|
|
991
|
+
* Possible values:
|
|
992
|
+
* - `0`: Unknown
|
|
993
|
+
* - `1`: Unknown
|
|
994
|
+
* - `2`: Unknown
|
|
995
|
+
*/
|
|
996
|
+
FinalizedState: {
|
|
997
|
+
/**
|
|
998
|
+
* The format type of the data.
|
|
999
|
+
*/
|
|
1000
|
+
type: "int",
|
|
1001
|
+
/**
|
|
1002
|
+
* How many bytes this integer is.
|
|
1003
|
+
*/
|
|
1004
|
+
bytes: 4,
|
|
1005
|
+
/**
|
|
1006
|
+
* The endianness of the data.
|
|
1007
|
+
*/
|
|
1008
|
+
format: "LE",
|
|
1009
|
+
/**
|
|
1010
|
+
* The signedness of the data.
|
|
1011
|
+
*/
|
|
1012
|
+
signed: false,
|
|
1013
|
+
/**
|
|
1014
|
+
* The default value to use when initializing a new entry.
|
|
1015
|
+
*
|
|
1016
|
+
* Bytes:
|
|
1017
|
+
* ```json
|
|
1018
|
+
* [0,0,0,0]
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
1021
|
+
defaultValue: Buffer.from([0, 0, 0, 0]),
|
|
1022
|
+
},
|
|
1023
|
+
/**
|
|
1024
|
+
* @deprecated No longer used.
|
|
1025
|
+
*
|
|
1026
|
+
* @todo Figure out how to parse this.
|
|
1027
|
+
* @todo Add a description for this.
|
|
1028
|
+
*/
|
|
1029
|
+
ConversionData: {
|
|
1030
|
+
/**
|
|
1031
|
+
* The format type of the data.
|
|
1032
|
+
*/
|
|
1033
|
+
type: "unknown",
|
|
1034
|
+
},
|
|
1035
|
+
/**
|
|
1036
|
+
* @todo Figure out how to parse this.
|
|
1037
|
+
* @todo Add a description for this.
|
|
1038
|
+
*/
|
|
1039
|
+
BorderBlocks: {
|
|
1040
|
+
/**
|
|
1041
|
+
* The format type of the data.
|
|
1042
|
+
*/
|
|
1043
|
+
type: "unknown",
|
|
1044
|
+
},
|
|
1045
|
+
/**
|
|
1046
|
+
* @todo Check if this still exists.
|
|
1047
|
+
* @todo Figure out how to parse this.
|
|
1048
|
+
* @todo Add a description for this.
|
|
1049
|
+
*/
|
|
1050
|
+
HardcodedSpawners: {
|
|
1051
|
+
/**
|
|
1052
|
+
* The format type of the data.
|
|
1053
|
+
*/
|
|
1054
|
+
type: "unknown",
|
|
1055
|
+
},
|
|
1056
|
+
/**
|
|
1057
|
+
* @see {@link NBTSchemas.nbtSchemas.RandomTicks}
|
|
1058
|
+
*
|
|
1059
|
+
* @todo Add a description for this.
|
|
1060
|
+
*/
|
|
1061
|
+
RandomTicks: {
|
|
1062
|
+
/**
|
|
1063
|
+
* The format type of the data.
|
|
1064
|
+
*/
|
|
1065
|
+
type: "NBT",
|
|
1066
|
+
// TO-DO: Add a default value for this.
|
|
1067
|
+
},
|
|
1068
|
+
/**
|
|
1069
|
+
* @deprecated Only used in versions < 1.18.0.
|
|
1070
|
+
*
|
|
1071
|
+
* @todo Figure out how to parse this.
|
|
1072
|
+
* @todo Add a description for this.
|
|
1073
|
+
*/
|
|
1074
|
+
Checksums: {
|
|
1075
|
+
/**
|
|
1076
|
+
* The format type of the data.
|
|
1077
|
+
*/
|
|
1078
|
+
type: "unknown",
|
|
1079
|
+
},
|
|
1080
|
+
/**
|
|
1081
|
+
* @todo Check if this still exists.
|
|
1082
|
+
* @todo Figure out how to parse this.
|
|
1083
|
+
* @todo Add a description for this.
|
|
1084
|
+
*/
|
|
1085
|
+
MetaDataHash: {
|
|
1086
|
+
/**
|
|
1087
|
+
* The format type of the data.
|
|
1088
|
+
*/
|
|
1089
|
+
type: "unknown",
|
|
1090
|
+
},
|
|
1091
|
+
/**
|
|
1092
|
+
* @deprecated Unused.
|
|
1093
|
+
*
|
|
1094
|
+
* @todo Figure out how to parse this.
|
|
1095
|
+
* @todo Add a description for this.
|
|
1096
|
+
*/
|
|
1097
|
+
GeneratedPreCavesAndCliffsBlending: {
|
|
1098
|
+
/**
|
|
1099
|
+
* The format type of the data.
|
|
1100
|
+
*/
|
|
1101
|
+
type: "unknown",
|
|
1102
|
+
},
|
|
1103
|
+
/**
|
|
1104
|
+
* @todo Figure out how to parse this.
|
|
1105
|
+
* @todo Add a description for this.
|
|
1106
|
+
*/
|
|
1107
|
+
BlendingBiomeHeight: {
|
|
1108
|
+
/**
|
|
1109
|
+
* The format type of the data.
|
|
1110
|
+
*/
|
|
1111
|
+
type: "unknown",
|
|
1112
|
+
},
|
|
1113
|
+
/**
|
|
1114
|
+
* @todo Figure out how to parse this.
|
|
1115
|
+
* @todo Add a description for this.
|
|
1116
|
+
*/
|
|
1117
|
+
BlendingData: {
|
|
1118
|
+
/**
|
|
1119
|
+
* The format type of the data.
|
|
1120
|
+
*/
|
|
1121
|
+
type: "unknown",
|
|
1122
|
+
},
|
|
1123
|
+
/**
|
|
1124
|
+
* @todo Figure out how to parse this.
|
|
1125
|
+
* @todo Add a description for this.
|
|
1126
|
+
*
|
|
1127
|
+
* Seems to always be one byte with a value of `0x00`.
|
|
1128
|
+
*/
|
|
1129
|
+
ActorDigestVersion: {
|
|
1130
|
+
/**
|
|
1131
|
+
* The format type of the data.
|
|
1132
|
+
*/
|
|
1133
|
+
type: "unknown",
|
|
1134
|
+
},
|
|
1135
|
+
/**
|
|
1136
|
+
* @deprecated Only used in versions < 1.16.100. Later versions use {@link entryContentTypeToFormatMap.Version}
|
|
1137
|
+
*
|
|
1138
|
+
* @todo Add a description for this.
|
|
1139
|
+
*/
|
|
1140
|
+
LegacyVersion: {
|
|
1141
|
+
/**
|
|
1142
|
+
* The format type of the data.
|
|
1143
|
+
*/
|
|
1144
|
+
type: "int",
|
|
1145
|
+
/**
|
|
1146
|
+
* How many bytes this integer is.
|
|
1147
|
+
*/
|
|
1148
|
+
bytes: 1,
|
|
1149
|
+
/**
|
|
1150
|
+
* The endianness of the data.
|
|
1151
|
+
*/
|
|
1152
|
+
format: "LE",
|
|
1153
|
+
/**
|
|
1154
|
+
* The signedness of the data.
|
|
1155
|
+
*/
|
|
1156
|
+
signed: false,
|
|
1157
|
+
},
|
|
1158
|
+
/**
|
|
1159
|
+
* @see {@link NBTSchemas.nbtSchemas.VillageDwellers}
|
|
1160
|
+
*
|
|
1161
|
+
* @todo Add a description for this.
|
|
1162
|
+
*/
|
|
1163
|
+
VillageDwellers: {
|
|
1164
|
+
/**
|
|
1165
|
+
* The format type of the data.
|
|
1166
|
+
*/
|
|
1167
|
+
type: "NBT",
|
|
1168
|
+
},
|
|
1169
|
+
/**
|
|
1170
|
+
* @see {@link NBTSchemas.nbtSchemas.VillageInfo}
|
|
1171
|
+
*
|
|
1172
|
+
* @todo Add a description for this.
|
|
1173
|
+
*/
|
|
1174
|
+
VillageInfo: {
|
|
1175
|
+
/**
|
|
1176
|
+
* The format type of the data.
|
|
1177
|
+
*/
|
|
1178
|
+
type: "NBT",
|
|
1179
|
+
},
|
|
1180
|
+
/**
|
|
1181
|
+
* @see {@link NBTSchemas.nbtSchemas.VillagePOI}
|
|
1182
|
+
*
|
|
1183
|
+
* @todo Add a description for this.
|
|
1184
|
+
*/
|
|
1185
|
+
VillagePOI: {
|
|
1186
|
+
/**
|
|
1187
|
+
* The format type of the data.
|
|
1188
|
+
*/
|
|
1189
|
+
type: "NBT",
|
|
1190
|
+
},
|
|
1191
|
+
/**
|
|
1192
|
+
* @see {@link NBTSchemas.nbtSchemas.VillagePlayers}
|
|
1193
|
+
*
|
|
1194
|
+
* @todo Add a description for this.
|
|
1195
|
+
*/
|
|
1196
|
+
VillagePlayers: {
|
|
1197
|
+
/**
|
|
1198
|
+
* The format type of the data.
|
|
1199
|
+
*/
|
|
1200
|
+
type: "NBT",
|
|
1201
|
+
},
|
|
1202
|
+
/**
|
|
1203
|
+
* A player's data.
|
|
1204
|
+
*
|
|
1205
|
+
* @see {@link NBTSchemas.nbtSchemas.Player}
|
|
1206
|
+
*/
|
|
1207
|
+
Player: {
|
|
1208
|
+
/**
|
|
1209
|
+
* The format type of the data.
|
|
1210
|
+
*/
|
|
1211
|
+
type: "NBT",
|
|
1212
|
+
},
|
|
1213
|
+
/**
|
|
1214
|
+
* A player's client data.
|
|
1215
|
+
*
|
|
1216
|
+
* 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.
|
|
1217
|
+
*
|
|
1218
|
+
* @see {@link NBTSchemas.nbtSchemas.PlayerClient}
|
|
1219
|
+
*/
|
|
1220
|
+
PlayerClient: {
|
|
1221
|
+
/**
|
|
1222
|
+
* The format type of the data.
|
|
1223
|
+
*/
|
|
1224
|
+
type: "NBT",
|
|
1225
|
+
},
|
|
1226
|
+
/**
|
|
1227
|
+
* The data for an entity in the world.
|
|
1228
|
+
*
|
|
1229
|
+
* @see {@link NBTSchemas.nbtSchemas.ActorPrefix}
|
|
1230
|
+
*/
|
|
1231
|
+
ActorPrefix: {
|
|
1232
|
+
/**
|
|
1233
|
+
* The format type of the data.
|
|
1234
|
+
*/
|
|
1235
|
+
type: "NBT",
|
|
1236
|
+
},
|
|
1237
|
+
/**
|
|
1238
|
+
* A list of entity IDs in a chunk.
|
|
1239
|
+
*
|
|
1240
|
+
* These IDs are the ones used in the entities' LevelDB keys.
|
|
1241
|
+
*
|
|
1242
|
+
* The IDs are 32-bit signed integers.
|
|
1243
|
+
*/
|
|
1244
|
+
Digest: {
|
|
1245
|
+
/**
|
|
1246
|
+
* The format type of the data.
|
|
1247
|
+
*/
|
|
1248
|
+
type: "custom",
|
|
1249
|
+
/**
|
|
1250
|
+
* The format type that results from the {@link entryContentTypeToFormatMap.Entity.parse | parse} method.
|
|
1251
|
+
*/
|
|
1252
|
+
resultType: "JSONNBT",
|
|
1253
|
+
/**
|
|
1254
|
+
* The function to parse the data.
|
|
1255
|
+
*
|
|
1256
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
1257
|
+
*
|
|
1258
|
+
* @param data The data to parse, as a buffer.
|
|
1259
|
+
* @returns A promise that resolves with the parsed data.
|
|
1260
|
+
*
|
|
1261
|
+
* @throws {any} If an error occurs while parsing the data.
|
|
1262
|
+
*/
|
|
1263
|
+
async parse(data: Buffer): Promise<{
|
|
1264
|
+
type: "compound";
|
|
1265
|
+
value: {
|
|
1266
|
+
entityIds: {
|
|
1267
|
+
type: "list";
|
|
1268
|
+
value: { type: "list"; value: { type: "int"; value: [id1: number, id2: number] }[] };
|
|
1269
|
+
};
|
|
1270
|
+
};
|
|
1271
|
+
}> {
|
|
1272
|
+
const entityIds: { type: "int"; value: [id1: number, id2: number] }[] = [];
|
|
1273
|
+
for (let i = 0; i < data.length; i += 8) {
|
|
1274
|
+
entityIds.push({ type: "int", value: [data.readInt32LE(i), data.readInt32LE(i + 4)] });
|
|
1275
|
+
}
|
|
1276
|
+
return {
|
|
1277
|
+
type: "compound",
|
|
1278
|
+
value: {
|
|
1279
|
+
entityIds: {
|
|
1280
|
+
type: "list",
|
|
1281
|
+
value: {
|
|
1282
|
+
type: "list",
|
|
1283
|
+
value: entityIds,
|
|
1284
|
+
},
|
|
1285
|
+
},
|
|
1286
|
+
},
|
|
1287
|
+
};
|
|
1288
|
+
},
|
|
1289
|
+
/**
|
|
1290
|
+
* The function to serialize the data.
|
|
1291
|
+
*
|
|
1292
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
1293
|
+
*
|
|
1294
|
+
* @param data The data to serialize.
|
|
1295
|
+
* @returns The serialized data, as a buffer.
|
|
1296
|
+
*/
|
|
1297
|
+
serialize(data: {
|
|
1298
|
+
type: "compound";
|
|
1299
|
+
value: {
|
|
1300
|
+
entityIds: {
|
|
1301
|
+
type: "list";
|
|
1302
|
+
value: { type: "list"; value: { type: "int"; value: [id1: number, id2: number] }[] };
|
|
1303
|
+
};
|
|
1304
|
+
};
|
|
1305
|
+
}): Buffer<ArrayBuffer> {
|
|
1306
|
+
const rawData: Buffer<ArrayBuffer>[] = data.value.entityIds.value.value.map((entityIds): Buffer<ArrayBuffer> => {
|
|
1307
|
+
const buffer: Buffer<ArrayBuffer> = Buffer.alloc(8);
|
|
1308
|
+
buffer.writeInt32LE(entityIds.value[0], 0);
|
|
1309
|
+
buffer.writeInt32LE(entityIds.value[1], 4);
|
|
1310
|
+
return buffer;
|
|
1311
|
+
});
|
|
1312
|
+
return Buffer.concat(rawData);
|
|
1313
|
+
},
|
|
1314
|
+
},
|
|
1315
|
+
/**
|
|
1316
|
+
* The data for a map.
|
|
1317
|
+
*
|
|
1318
|
+
* This includes things such as location, image data, and decorations.
|
|
1319
|
+
*
|
|
1320
|
+
* @see {@link NBTSchemas.nbtSchemas.Map}
|
|
1321
|
+
*/
|
|
1322
|
+
Map: {
|
|
1323
|
+
/**
|
|
1324
|
+
* The format type of the data.
|
|
1325
|
+
*/
|
|
1326
|
+
type: "NBT",
|
|
1327
|
+
},
|
|
1328
|
+
/**
|
|
1329
|
+
* The content type of the `portals` LevelDB key, which stores portal data.
|
|
1330
|
+
*
|
|
1331
|
+
* @see {@link NBTSchemas.nbtSchemas.Portals}
|
|
1332
|
+
*/
|
|
1333
|
+
Portals: {
|
|
1334
|
+
/**
|
|
1335
|
+
* The format type of the data.
|
|
1336
|
+
*/
|
|
1337
|
+
type: "NBT",
|
|
1338
|
+
},
|
|
1339
|
+
/**
|
|
1340
|
+
* The content type of the `schedulerWT` LevelDB key, which stores wandering trader data.
|
|
1341
|
+
*
|
|
1342
|
+
* @see {@link NBTSchemas.nbtSchemas.SchedulerWT}
|
|
1343
|
+
*/
|
|
1344
|
+
SchedulerWT: {
|
|
1345
|
+
/**
|
|
1346
|
+
* The format type of the data.
|
|
1347
|
+
*/
|
|
1348
|
+
type: "NBT",
|
|
1349
|
+
},
|
|
1350
|
+
/**
|
|
1351
|
+
* Date for a structure (the kind saved by a structure block or the [`/structure`](https://minecraft.wiki/w/Commands/structure) command).
|
|
1352
|
+
*
|
|
1353
|
+
* @see {@link NBTSchemas.nbtSchemas.StructureTemplate}
|
|
1354
|
+
*/
|
|
1355
|
+
StructureTemplate: {
|
|
1356
|
+
/**
|
|
1357
|
+
* The format type of the data.
|
|
1358
|
+
*/
|
|
1359
|
+
type: "NBT",
|
|
1360
|
+
/**
|
|
1361
|
+
* The raw file extension of the data.
|
|
1362
|
+
*/
|
|
1363
|
+
rawFileExtension: "mcstructure",
|
|
1364
|
+
},
|
|
1365
|
+
/**
|
|
1366
|
+
* A ticking area, either from an entity or the [`/tickingarea`](https://minecraft.wiki/w/Commands/tickingarea) command.
|
|
1367
|
+
*
|
|
1368
|
+
* @see {@link NBTSchemas.nbtSchemas.TickingArea}
|
|
1369
|
+
*/
|
|
1370
|
+
TickingArea: {
|
|
1371
|
+
/**
|
|
1372
|
+
* The format type of the data.
|
|
1373
|
+
*/
|
|
1374
|
+
type: "NBT",
|
|
1375
|
+
},
|
|
1376
|
+
/**
|
|
1377
|
+
* @deprecated Only used in versions < 1.5.0.
|
|
1378
|
+
*
|
|
1379
|
+
* @todo Add a description for this.
|
|
1380
|
+
*/
|
|
1381
|
+
FlatWorldLayers: {
|
|
1382
|
+
/**
|
|
1383
|
+
* The format type of the data.
|
|
1384
|
+
*/
|
|
1385
|
+
type: "ASCII",
|
|
1386
|
+
},
|
|
1387
|
+
/**
|
|
1388
|
+
* The scoreboard data (ex. from the [`/scoreboard`](https://minecraft.wiki/w/Commands/scoreboard) command).
|
|
1389
|
+
*
|
|
1390
|
+
* @see {@link NBTSchemas.nbtSchemas.Scoreboard}
|
|
1391
|
+
*/
|
|
1392
|
+
Scoreboard: {
|
|
1393
|
+
/**
|
|
1394
|
+
* The format type of the data.
|
|
1395
|
+
*/
|
|
1396
|
+
type: "NBT",
|
|
1397
|
+
},
|
|
1398
|
+
/**
|
|
1399
|
+
* @todo Add a schema for this.
|
|
1400
|
+
* @todo Add a description for this.
|
|
1401
|
+
*/
|
|
1402
|
+
Dimension: {
|
|
1403
|
+
/**
|
|
1404
|
+
* The format type of the data.
|
|
1405
|
+
*/
|
|
1406
|
+
type: "NBT",
|
|
1407
|
+
},
|
|
1408
|
+
/**
|
|
1409
|
+
* The content type of the `AutonomousEntities` LevelDB key, which stores a list of autonomous entities.
|
|
1410
|
+
*
|
|
1411
|
+
* @see {@link NBTSchemas.nbtSchemas.AutonomousEntities}
|
|
1412
|
+
*
|
|
1413
|
+
* @todo Add a better description for this, that includes what an autonomous entity is.
|
|
1414
|
+
*/
|
|
1415
|
+
AutonomousEntities: {
|
|
1416
|
+
/**
|
|
1417
|
+
* The format type of the data.
|
|
1418
|
+
*/
|
|
1419
|
+
type: "NBT",
|
|
1420
|
+
},
|
|
1421
|
+
/**
|
|
1422
|
+
* The content type of the `BiomeData` LevelDB key, which stores data for certain biome types.
|
|
1423
|
+
*
|
|
1424
|
+
* @see {@link NBTSchemas.nbtSchemas.BiomeData}
|
|
1425
|
+
*/
|
|
1426
|
+
BiomeData: {
|
|
1427
|
+
/**
|
|
1428
|
+
* The format type of the data.
|
|
1429
|
+
*/
|
|
1430
|
+
type: "NBT",
|
|
1431
|
+
},
|
|
1432
|
+
/**
|
|
1433
|
+
* The content type of the `mobevents` LevelDB key, which stores what mob events are enabled or disabled.
|
|
1434
|
+
*
|
|
1435
|
+
* @see {@link NBTSchemas.nbtSchemas.MobEvents}
|
|
1436
|
+
*/
|
|
1437
|
+
MobEvents: {
|
|
1438
|
+
/**
|
|
1439
|
+
* The format type of the data.
|
|
1440
|
+
*/
|
|
1441
|
+
type: "NBT",
|
|
1442
|
+
// TO-DO: Add a default value for this.
|
|
1443
|
+
},
|
|
1444
|
+
/**
|
|
1445
|
+
* The content type of the `level.dat` and `level.dat_old` files.
|
|
1446
|
+
*
|
|
1447
|
+
* This stores all the world settings.
|
|
1448
|
+
*
|
|
1449
|
+
* @see {@link NBTSchemas.nbtSchemas.LevelDat}
|
|
1450
|
+
*/
|
|
1451
|
+
LevelDat: {
|
|
1452
|
+
/**
|
|
1453
|
+
* The format type of the data.
|
|
1454
|
+
*/
|
|
1455
|
+
type: "custom",
|
|
1456
|
+
/**
|
|
1457
|
+
* The format type that results from the {@link entryContentTypeToFormatMap.LevelDat.parse | parse} method.
|
|
1458
|
+
*/
|
|
1459
|
+
resultType: "JSONNBT",
|
|
1460
|
+
/**
|
|
1461
|
+
* The function to parse the data.
|
|
1462
|
+
*
|
|
1463
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
1464
|
+
*
|
|
1465
|
+
* @param data The data to parse, as a buffer.
|
|
1466
|
+
* @returns A promise that resolves with the parsed data.
|
|
1467
|
+
*
|
|
1468
|
+
* @throws {any} If an error occurs while parsing the data.
|
|
1469
|
+
*/
|
|
1470
|
+
async parse(data: Buffer): Promise<NBTSchemas.NBTSchemaTypes.LevelDat> {
|
|
1471
|
+
return (await NBT.parse(data)).parsed;
|
|
1472
|
+
},
|
|
1473
|
+
/**
|
|
1474
|
+
* The function to serialize the data.
|
|
1475
|
+
*
|
|
1476
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
1477
|
+
*
|
|
1478
|
+
* @param data The data to serialize.
|
|
1479
|
+
* @returns The serialized data, as a buffer.
|
|
1480
|
+
*
|
|
1481
|
+
* @throws {TypeError} If {@link data} has a name property at the top level that is not of type string.
|
|
1482
|
+
*/
|
|
1483
|
+
serialize(data: NBTSchemas.NBTSchemaTypes.LevelDat): Buffer<ArrayBuffer> {
|
|
1484
|
+
const nbtData: Buffer = NBT.writeUncompressed({ name: "", ...data }, "little");
|
|
1485
|
+
return Buffer.concat([Buffer.from("0A000000", "hex"), writeSpecificIntType(Buffer.alloc(4), BigInt(nbtData.length), 4, "LE", false), nbtData]);
|
|
1486
|
+
},
|
|
1487
|
+
/**
|
|
1488
|
+
* The default value to use when initializing a new entry.
|
|
1489
|
+
*
|
|
1490
|
+
* @todo Add a link to the object with the default level.dat value once it is made.
|
|
1491
|
+
*/
|
|
1492
|
+
get defaultValue(): Buffer<ArrayBuffer> {
|
|
1493
|
+
// TO-DO: Add a full default level.dat value.
|
|
1494
|
+
const nbtData: Buffer = NBT.writeUncompressed(
|
|
1495
|
+
{ name: "", type: "compound", value: {} } as /* @todo Remove this partial later. */ Partial<NBTSchemas.NBTSchemaTypes.LevelDat> & NBT.NBT,
|
|
1496
|
+
"little"
|
|
1497
|
+
);
|
|
1498
|
+
const result: Buffer<ArrayBuffer> = Buffer.concat([
|
|
1499
|
+
Buffer.from("0A000000", "hex"),
|
|
1500
|
+
writeSpecificIntType(Buffer.alloc(4), BigInt(nbtData.length), 4, "LE", false),
|
|
1501
|
+
nbtData,
|
|
1502
|
+
]);
|
|
1503
|
+
Object.defineProperty(this, "defaultValue", { value: result, configurable: true, enumerable: true, writable: false });
|
|
1504
|
+
return result;
|
|
1505
|
+
},
|
|
1506
|
+
/**
|
|
1507
|
+
* The raw file extension of the data.
|
|
1508
|
+
*/
|
|
1509
|
+
rawFileExtension: "dat",
|
|
1510
|
+
},
|
|
1511
|
+
/**
|
|
1512
|
+
* @todo Figure out how to parse this.
|
|
1513
|
+
* @todo Add a description for this.
|
|
1514
|
+
*/
|
|
1515
|
+
AABBVolumes: {
|
|
1516
|
+
/**
|
|
1517
|
+
* The format type of the data.
|
|
1518
|
+
*/
|
|
1519
|
+
type: "unknown",
|
|
1520
|
+
},
|
|
1521
|
+
/**
|
|
1522
|
+
* The content type of the `DynamicProperties` LevelDB key, which stores dynamic properties data for add-ons.
|
|
1523
|
+
*
|
|
1524
|
+
* @see {@link NBTSchemas.nbtSchemas.DynamicProperties}
|
|
1525
|
+
*/
|
|
1526
|
+
DynamicProperties: {
|
|
1527
|
+
/**
|
|
1528
|
+
* The format type of the data.
|
|
1529
|
+
*/
|
|
1530
|
+
type: "NBT",
|
|
1531
|
+
/**
|
|
1532
|
+
* The default value to use when initializing a new entry.
|
|
1533
|
+
*
|
|
1534
|
+
* Value:
|
|
1535
|
+
* ```json
|
|
1536
|
+
* { "name": "", "type": "compound", "value": {} }
|
|
1537
|
+
* ```
|
|
1538
|
+
*/
|
|
1539
|
+
defaultValue: NBT.writeUncompressed({ name: "", type: "compound", value: {} }, "little"),
|
|
1540
|
+
},
|
|
1541
|
+
/**
|
|
1542
|
+
* @todo Figure out what the little bits of data before each of the NBT data chunks are, and what the 4 bytes at the very beginning are.
|
|
1543
|
+
* @todo Add a description for this.
|
|
1544
|
+
*
|
|
1545
|
+
* `Array.from(data).reduce((a, b, i, ar)=>[...a, ...(Array.from(Buffer.from("0a000008", "hex")).every((v, ib)=>v === ar[i + ib]) ? [i] : [])], [])`
|
|
1546
|
+
*
|
|
1547
|
+
* ```js
|
|
1548
|
+
* const tab = tabManager.selectedTab
|
|
1549
|
+
* let dataN = await tab.db.get(tab.cachedDBKeys.LevelChunkMetaDataDictionary[0]);
|
|
1550
|
+
* let dataNB = [];
|
|
1551
|
+
* Array.from(dataN).reduce((a, b, i, ar)=>[...a, ...(Array.from(Buffer.from("0a000008", "hex")).every((v, ib)=>v === ar[i + ib]) ? [i] : [])], []).forEach((v, i, a)=>dataNB.push({t: 1, v: dataN.slice(v - (i === 0 ? 12 : 8), v)}, {t: 2, v: dataN.slice(v, (a[i + 1] !== undefined ? a[i + 1] - 8 : undefined))}))
|
|
1552
|
+
* const dataNc = await Promise.all(dataNB.map(async (v, i)=>{try{console.log(i, v); return (v.t === 1 ? v : await require("prismarine-nbt").parse(v.v, "little"))} catch (e) {console.error(e, i, v)}}))
|
|
1553
|
+
* dataNc.forEach(v=>v.t === 1 ? void 0 : (v.parsed.value.LastSavedDimensionHeightRange ? (v.parsed.value.LastSavedDimensionHeightRange.value.min.value = -100, v.parsed.value.LastSavedDimensionHeightRange.value.max.value = 400) : void 0, v.parsed.value.OriginalDimensionHeightRange ? (v.parsed.value.OriginalDimensionHeightRange.value.min.value = -100, v.parsed.value.OriginalDimensionHeightRange.value.max.value = 400) : void 0))
|
|
1554
|
+
* const dataNd = await Promise.all(datac.map(async v=>(v.t === 1 ? v.v : await require("prismarine-nbt").writeUncompressed(v.parsed, "little"))))
|
|
1555
|
+
* tab.db.put(tab.cachedDBKeys.LevelChunkMetaDataDictionary[0], Buffer.concat(dataNd))
|
|
1556
|
+
* ```
|
|
1557
|
+
*/
|
|
1558
|
+
LevelChunkMetaDataDictionary: {
|
|
1559
|
+
/**
|
|
1560
|
+
* The format type of the data.
|
|
1561
|
+
*/
|
|
1562
|
+
type: "unknown",
|
|
1563
|
+
},
|
|
1564
|
+
/**
|
|
1565
|
+
* @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.)
|
|
1566
|
+
* @todo Add a description for this.
|
|
1567
|
+
*/
|
|
1568
|
+
RealmsStoriesData: {
|
|
1569
|
+
/**
|
|
1570
|
+
* The format type of the data.
|
|
1571
|
+
*/
|
|
1572
|
+
type: "unknown",
|
|
1573
|
+
},
|
|
1574
|
+
/**
|
|
1575
|
+
* The content type used for LevelDB keys that are used for the forced world corruption feature of the developer version of Bedrock Edition.
|
|
1576
|
+
*
|
|
1577
|
+
* These keys are normally only found when the [`/corruptworld`](https://minecraft.wiki/w/Commands/corruptworld) command is used.
|
|
1578
|
+
*
|
|
1579
|
+
* Removing these keys fixes the forced world corruption.
|
|
1580
|
+
*/
|
|
1581
|
+
ForcedWorldCorruption: {
|
|
1582
|
+
/**
|
|
1583
|
+
* The format type of the data.
|
|
1584
|
+
*/
|
|
1585
|
+
type: "UTF-8",
|
|
1586
|
+
/**
|
|
1587
|
+
* The default value to use when initializing a new entry.
|
|
1588
|
+
*
|
|
1589
|
+
* Value:
|
|
1590
|
+
* ```typescript
|
|
1591
|
+
* Buffer.from("true", "utf-8")
|
|
1592
|
+
* ```
|
|
1593
|
+
*/
|
|
1594
|
+
defaultValue: Buffer.from("true", "utf-8"),
|
|
1595
|
+
},
|
|
1596
|
+
/**
|
|
1597
|
+
* @todo Add a schema for this.
|
|
1598
|
+
* @todo Add a description for this.
|
|
1599
|
+
*/
|
|
1600
|
+
ChunkLoadedRequest: {
|
|
1601
|
+
/**
|
|
1602
|
+
* The format type of the data.
|
|
1603
|
+
*/
|
|
1604
|
+
type: "NBT",
|
|
1605
|
+
// TO-DO: Add a default value for this.
|
|
1606
|
+
},
|
|
1607
|
+
/**
|
|
1608
|
+
* All data that has a key that is not recognized.
|
|
1609
|
+
*/
|
|
1610
|
+
Unknown: {
|
|
1611
|
+
/**
|
|
1612
|
+
* The format type of the data.
|
|
1613
|
+
*/
|
|
1614
|
+
type: "unknown",
|
|
1615
|
+
},
|
|
1616
|
+
} as const satisfies {
|
|
1617
|
+
[key in DBEntryContentType]: EntryContentTypeFormatData;
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* The format data for an entry content type.
|
|
1622
|
+
*
|
|
1623
|
+
* 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.
|
|
1624
|
+
*
|
|
1625
|
+
* This is used in the {@link entryContentTypeToFormatMap} object.
|
|
1626
|
+
*/
|
|
1627
|
+
export type EntryContentTypeFormatData = (
|
|
1628
|
+
| {
|
|
1629
|
+
/**
|
|
1630
|
+
* The format type of the data.
|
|
1631
|
+
*/
|
|
1632
|
+
readonly type: "JSON" | "SNBT" | "ASCII" | "binary" | "binaryPlainText" | "hex" | "UTF-8" | "unknown";
|
|
1633
|
+
}
|
|
1634
|
+
| {
|
|
1635
|
+
/**
|
|
1636
|
+
* The format type of the data.
|
|
1637
|
+
*/
|
|
1638
|
+
readonly type: "NBT";
|
|
1639
|
+
/**
|
|
1640
|
+
* The endianness of the data.
|
|
1641
|
+
*
|
|
1642
|
+
* If not present, `"LE"` should be assumed.
|
|
1643
|
+
*
|
|
1644
|
+
* - `"BE"`: Big Endian
|
|
1645
|
+
* - `"LE"`: Little Endian
|
|
1646
|
+
* - `"LEV"`: Little Varint
|
|
1647
|
+
*
|
|
1648
|
+
* @default "LE"
|
|
1649
|
+
*/
|
|
1650
|
+
readonly format?: "BE" | "LE" | "LEV";
|
|
1651
|
+
}
|
|
1652
|
+
| {
|
|
1653
|
+
/**
|
|
1654
|
+
* The format type of the data.
|
|
1655
|
+
*/
|
|
1656
|
+
readonly type: "int";
|
|
1657
|
+
/**
|
|
1658
|
+
* How many bytes this integer is.
|
|
1659
|
+
*/
|
|
1660
|
+
readonly bytes: number;
|
|
1661
|
+
/**
|
|
1662
|
+
* The endianness of the data.
|
|
1663
|
+
*/
|
|
1664
|
+
readonly format: "BE" | "LE";
|
|
1665
|
+
/**
|
|
1666
|
+
* The signedness of the data.
|
|
1667
|
+
*/
|
|
1668
|
+
readonly signed: boolean;
|
|
1669
|
+
}
|
|
1670
|
+
| {
|
|
1671
|
+
/**
|
|
1672
|
+
* The format type of the data.
|
|
1673
|
+
*/
|
|
1674
|
+
readonly type: "custom";
|
|
1675
|
+
/**
|
|
1676
|
+
* The format type that results from the {@link parse} method.
|
|
1677
|
+
*/
|
|
1678
|
+
readonly resultType: "JSONNBT";
|
|
1679
|
+
/**
|
|
1680
|
+
* The function to parse the data.
|
|
1681
|
+
*
|
|
1682
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
1683
|
+
*
|
|
1684
|
+
* @param data The data to parse, as a buffer.
|
|
1685
|
+
* @returns The parsed data.
|
|
1686
|
+
*/
|
|
1687
|
+
parse(data: Buffer): NBT.Compound | Promise<NBT.Compound>;
|
|
1688
|
+
/**
|
|
1689
|
+
* The function to serialize the data.
|
|
1690
|
+
*
|
|
1691
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
1692
|
+
*
|
|
1693
|
+
* @param data The data to serialize.
|
|
1694
|
+
* @returns The serialized data, as a buffer.
|
|
1695
|
+
*/
|
|
1696
|
+
serialize(data: NBT.Compound): Buffer | Promise<Buffer>;
|
|
1697
|
+
}
|
|
1698
|
+
| {
|
|
1699
|
+
/**
|
|
1700
|
+
* The format type of the data.
|
|
1701
|
+
*/
|
|
1702
|
+
readonly type: "custom";
|
|
1703
|
+
/**
|
|
1704
|
+
* The format type that results from the {@link parse} method.
|
|
1705
|
+
*/
|
|
1706
|
+
readonly resultType: "SNBT";
|
|
1707
|
+
/**
|
|
1708
|
+
* The function to parse the data.
|
|
1709
|
+
*
|
|
1710
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
1711
|
+
*
|
|
1712
|
+
* @param data The data to parse, as a buffer.
|
|
1713
|
+
* @returns The parsed data.
|
|
1714
|
+
*/
|
|
1715
|
+
parse(data: Buffer): string | Promise<string>;
|
|
1716
|
+
/**
|
|
1717
|
+
* The function to serialize the data.
|
|
1718
|
+
*
|
|
1719
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
1720
|
+
*
|
|
1721
|
+
* @param data The data to serialize.
|
|
1722
|
+
* @returns The serialized data, as a buffer.
|
|
1723
|
+
*/
|
|
1724
|
+
serialize(data: string): Buffer | Promise<Buffer>;
|
|
1725
|
+
}
|
|
1726
|
+
| {
|
|
1727
|
+
/**
|
|
1728
|
+
* The format type of the data.
|
|
1729
|
+
*/
|
|
1730
|
+
readonly type: "custom";
|
|
1731
|
+
/**
|
|
1732
|
+
* The format type that results from the {@link parse} method.
|
|
1733
|
+
*/
|
|
1734
|
+
readonly resultType: "buffer";
|
|
1735
|
+
/**
|
|
1736
|
+
* The function to parse the data.
|
|
1737
|
+
*
|
|
1738
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
1739
|
+
*
|
|
1740
|
+
* @param data The data to parse, as a buffer.
|
|
1741
|
+
* @returns The parsed data.
|
|
1742
|
+
*/
|
|
1743
|
+
parse(data: Buffer): Buffer | Promise<Buffer>;
|
|
1744
|
+
/**
|
|
1745
|
+
* The function to serialize the data.
|
|
1746
|
+
*
|
|
1747
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
1748
|
+
*
|
|
1749
|
+
* @param data The data to serialize.
|
|
1750
|
+
* @returns The serialized data, as a buffer.
|
|
1751
|
+
*/
|
|
1752
|
+
serialize(data: Buffer): Buffer | Promise<Buffer>;
|
|
1753
|
+
}
|
|
1754
|
+
| {
|
|
1755
|
+
/**
|
|
1756
|
+
* The format type of the data.
|
|
1757
|
+
*/
|
|
1758
|
+
readonly type: "custom";
|
|
1759
|
+
/**
|
|
1760
|
+
* The format type that results from the {@link parse} method.
|
|
1761
|
+
*/
|
|
1762
|
+
readonly resultType: "unknown";
|
|
1763
|
+
/**
|
|
1764
|
+
* The function to parse the data.
|
|
1765
|
+
*
|
|
1766
|
+
* The {@link data} parameter should be the buffer read directly from the file or LevelDB entry.
|
|
1767
|
+
*
|
|
1768
|
+
* @param data The data to parse, as a buffer.
|
|
1769
|
+
* @returns The parsed data.
|
|
1770
|
+
*/
|
|
1771
|
+
parse(data: Buffer): any | Promise<any>;
|
|
1772
|
+
/**
|
|
1773
|
+
* The function to serialize the data.
|
|
1774
|
+
*
|
|
1775
|
+
* This result of this can be written directly to the file or LevelDB entry.
|
|
1776
|
+
*
|
|
1777
|
+
* @param data The data to serialize.
|
|
1778
|
+
* @returns The serialized data, as a buffer.
|
|
1779
|
+
*/
|
|
1780
|
+
serialize(data: any): Buffer | Promise<Buffer>;
|
|
1781
|
+
}
|
|
1782
|
+
) & {
|
|
1783
|
+
/**
|
|
1784
|
+
* The raw file extension of the data.
|
|
1785
|
+
*
|
|
1786
|
+
* If not present, `"bin"` should be assumed.
|
|
1787
|
+
*
|
|
1788
|
+
* @default "bin"
|
|
1789
|
+
*/
|
|
1790
|
+
readonly rawFileExtension?: string;
|
|
1791
|
+
/**
|
|
1792
|
+
* The default value to use when initializing a new entry.
|
|
1793
|
+
*
|
|
1794
|
+
* @default undefined
|
|
1795
|
+
*/
|
|
1796
|
+
readonly defaultValue?: Buffer;
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
//#endregion
|
|
1800
|
+
|
|
1801
|
+
// --------------------------------------------------------------------------------
|
|
1802
|
+
// Types
|
|
1803
|
+
// --------------------------------------------------------------------------------
|
|
1804
|
+
|
|
1805
|
+
//#region Types
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* Represents a two-directional vector.
|
|
1809
|
+
*/
|
|
1810
|
+
export interface Vector2 {
|
|
1811
|
+
/**
|
|
1812
|
+
* X component of this vector.
|
|
1813
|
+
*/
|
|
1814
|
+
x: number;
|
|
1815
|
+
/**
|
|
1816
|
+
* Y component of this vector.
|
|
1817
|
+
*/
|
|
1818
|
+
y: number;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Represents a two-directional vector with X and Z components.
|
|
1823
|
+
*/
|
|
1824
|
+
export interface VectorXZ {
|
|
1825
|
+
/**
|
|
1826
|
+
* X component of this vector.
|
|
1827
|
+
*/
|
|
1828
|
+
x: number;
|
|
1829
|
+
/**
|
|
1830
|
+
* Z component of this vector.
|
|
1831
|
+
*/
|
|
1832
|
+
z: number;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
/**
|
|
1836
|
+
* Represents a three-directional vector.
|
|
1837
|
+
*/
|
|
1838
|
+
export interface Vector3 {
|
|
1839
|
+
/**
|
|
1840
|
+
* X component of this vector.
|
|
1841
|
+
*/
|
|
1842
|
+
x: number;
|
|
1843
|
+
/**
|
|
1844
|
+
* Y component of this vector.
|
|
1845
|
+
*/
|
|
1846
|
+
y: number;
|
|
1847
|
+
/**
|
|
1848
|
+
* Z component of this vector.
|
|
1849
|
+
*/
|
|
1850
|
+
z: number;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
/**
|
|
1854
|
+
* An ID of a Minecraft dimension.
|
|
1855
|
+
*/
|
|
1856
|
+
export type Dimension = (typeof dimensions)[number];
|
|
1857
|
+
|
|
1858
|
+
/**
|
|
1859
|
+
* Represents a three-directional vector with an associated dimension.
|
|
1860
|
+
*/
|
|
1861
|
+
export interface DimensionLocation extends Vector3 {
|
|
1862
|
+
/**
|
|
1863
|
+
* Dimension that this coordinate is associated with.
|
|
1864
|
+
*/
|
|
1865
|
+
dimension: Dimension;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* Represents a two-directional vector with an associated dimension.
|
|
1870
|
+
*/
|
|
1871
|
+
export interface DimensionVector2 extends Vector2 {
|
|
1872
|
+
/**
|
|
1873
|
+
* Dimension that this coordinate is associated with.
|
|
1874
|
+
*/
|
|
1875
|
+
dimension: Dimension;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
/**
|
|
1879
|
+
* Represents a two-directional vector with X and Z components and an associated dimension.
|
|
1880
|
+
*/
|
|
1881
|
+
export interface DimensionVectorXZ extends VectorXZ {
|
|
1882
|
+
/**
|
|
1883
|
+
* Dimension that this coordinate is associated with.
|
|
1884
|
+
*/
|
|
1885
|
+
dimension: Dimension;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
/**
|
|
1889
|
+
* Represents a two-directional vector with X and Z components and an associated dimension and a sub-chunk index.
|
|
1890
|
+
*/
|
|
1891
|
+
export interface SubChunkIndexDimensionVectorXZ extends DimensionVectorXZ {
|
|
1892
|
+
/**
|
|
1893
|
+
* The index of this sub-chunk.
|
|
1894
|
+
*
|
|
1895
|
+
* Should be between 0 and 15 (inclusive).
|
|
1896
|
+
*/
|
|
1897
|
+
subChunkIndex: number;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
/**
|
|
1901
|
+
* @todo
|
|
1902
|
+
*/
|
|
1903
|
+
export interface StructureSectionData extends NBT.Compound {
|
|
1904
|
+
/**
|
|
1905
|
+
* The size of the structure, as a tuple of 3 integers.
|
|
1906
|
+
*/
|
|
1907
|
+
size: NBTSchemas.NBTSchemaTypes.StructureTemplate["value"]["size"];
|
|
1908
|
+
/**
|
|
1909
|
+
* The block indices.
|
|
1910
|
+
*
|
|
1911
|
+
* These are two arrays of indices in the block palette.
|
|
1912
|
+
*
|
|
1913
|
+
* The first layer is the block layer.
|
|
1914
|
+
*
|
|
1915
|
+
* The second layer is the waterlog layer, even though it is mainly used for waterlogging, other blocks can be put here to,
|
|
1916
|
+
* which allows for putting two blocks in the same location, or creating ghost blocks (as blocks in this layer cannot be interacted with,
|
|
1917
|
+
* however when the corresponding block in the block layer is broken, this block gets moved to the block layer).
|
|
1918
|
+
*/
|
|
1919
|
+
block_indices: {
|
|
1920
|
+
type: `${NBT.TagType.List}`;
|
|
1921
|
+
value: {
|
|
1922
|
+
type: `${NBT.TagType.List}`;
|
|
1923
|
+
value: [
|
|
1924
|
+
blockLayer: {
|
|
1925
|
+
type: `${NBT.TagType.Int}`;
|
|
1926
|
+
value: number[];
|
|
1927
|
+
},
|
|
1928
|
+
waterlogLayer: {
|
|
1929
|
+
type: `${NBT.TagType.Int}`;
|
|
1930
|
+
value: number[];
|
|
1931
|
+
},
|
|
1932
|
+
];
|
|
1933
|
+
};
|
|
1934
|
+
};
|
|
1935
|
+
/**
|
|
1936
|
+
* The block palette.
|
|
1937
|
+
*/
|
|
1938
|
+
palette: {
|
|
1939
|
+
type: `${NBT.TagType.List}`;
|
|
1940
|
+
value: {
|
|
1941
|
+
type: `${NBT.TagType.Compound}`;
|
|
1942
|
+
value: NBTSchemas.NBTSchemaTypes.Block["value"][];
|
|
1943
|
+
};
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
/**
|
|
1948
|
+
* Biome palette data.
|
|
1949
|
+
*/
|
|
1950
|
+
export interface BiomePalette {
|
|
1951
|
+
/**
|
|
1952
|
+
* The data for the individual blocks, or `null` if this sub-chunk has no biome data.
|
|
1953
|
+
*
|
|
1954
|
+
* The values of this map to the index of the biome in the palette.
|
|
1955
|
+
*/
|
|
1956
|
+
values: number[] | null;
|
|
1957
|
+
/**
|
|
1958
|
+
* The palette of biomes as a list of biome numeric IDs.
|
|
1959
|
+
*/
|
|
1960
|
+
palette: number[];
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
/**
|
|
1964
|
+
* The content type of a LevelDB entry.
|
|
1965
|
+
*/
|
|
1966
|
+
export type DBEntryContentType = (typeof DBEntryContentTypes)[number];
|
|
1967
|
+
|
|
1968
|
+
/**
|
|
1969
|
+
* A content type of a LevelDB chunk key entry.
|
|
1970
|
+
*/
|
|
1971
|
+
export type DBChunkKeyEntryContentType =
|
|
1972
|
+
| "Data3D"
|
|
1973
|
+
| "Version"
|
|
1974
|
+
| "Data2D"
|
|
1975
|
+
| "Data2DLegacy"
|
|
1976
|
+
| "SubChunkPrefix"
|
|
1977
|
+
| "LegacyTerrain"
|
|
1978
|
+
| "BlockEntity"
|
|
1979
|
+
| "Entity"
|
|
1980
|
+
| "PendingTicks"
|
|
1981
|
+
| "LegacyBlockExtraData"
|
|
1982
|
+
| "BiomeState"
|
|
1983
|
+
| "FinalizedState"
|
|
1984
|
+
| "ConversionData"
|
|
1985
|
+
| "BorderBlocks"
|
|
1986
|
+
| "HardcodedSpawners"
|
|
1987
|
+
| "RandomTicks"
|
|
1988
|
+
| "Checksums"
|
|
1989
|
+
| "MetaDataHash"
|
|
1990
|
+
| "GeneratedPreCavesAndCliffsBlending"
|
|
1991
|
+
| "BlendingBiomeHeight"
|
|
1992
|
+
| "BlendingData"
|
|
1993
|
+
| "ActorDigestVersion"
|
|
1994
|
+
| "LegacyVersion"
|
|
1995
|
+
| "AABBVolumes";
|
|
1996
|
+
|
|
1997
|
+
//#endregion
|
|
1998
|
+
|
|
1999
|
+
// --------------------------------------------------------------------------------
|
|
2000
|
+
// Functions
|
|
2001
|
+
// --------------------------------------------------------------------------------
|
|
2002
|
+
|
|
2003
|
+
//#region Functions
|
|
2004
|
+
|
|
2005
|
+
/**
|
|
2006
|
+
* Parses an integer from a buffer gives the number of bytes, endianness, signedness and offset.
|
|
2007
|
+
*
|
|
2008
|
+
* @param buffer The buffer to read from.
|
|
2009
|
+
* @param bytes The number of bytes to read.
|
|
2010
|
+
* @param format The endianness of the data.
|
|
2011
|
+
* @param signed The signedness of the data. Defaults to `false`.
|
|
2012
|
+
* @param offset The offset to read from. Defaults to `0`.
|
|
2013
|
+
* @returns The parsed integer.
|
|
2014
|
+
*
|
|
2015
|
+
* @throws {RangeError} If the byte length is less than 1.
|
|
2016
|
+
* @throws {RangeError} If the buffer does not contain enough data at the specified offset.
|
|
2017
|
+
*/
|
|
2018
|
+
export function parseSpecificIntType(buffer: Buffer, bytes: number, format: "BE" | "LE", signed: boolean = false, offset: number = 0): bigint {
|
|
2019
|
+
if (bytes < 1) {
|
|
2020
|
+
throw new RangeError("Byte length must be at least 1");
|
|
2021
|
+
}
|
|
2022
|
+
if (offset + bytes > buffer.length) {
|
|
2023
|
+
throw new RangeError("Buffer does not contain enough data at the specified offset");
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
let result: bigint = 0n;
|
|
2027
|
+
|
|
2028
|
+
if (format === "BE") {
|
|
2029
|
+
for (let i: number = 0; i < bytes; i++) {
|
|
2030
|
+
result = (result << 8n) | BigInt(buffer[offset + i]!);
|
|
2031
|
+
}
|
|
2032
|
+
} else {
|
|
2033
|
+
for (let i: number = bytes - 1; i >= 0; i--) {
|
|
2034
|
+
result = (result << 8n) | BigInt(buffer[offset + i]!);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
if (signed) {
|
|
2039
|
+
const signBit: bigint = 1n << BigInt(bytes * 8 - 1);
|
|
2040
|
+
if (result & signBit) {
|
|
2041
|
+
result -= 1n << BigInt(bytes * 8);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
return result;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
/**
|
|
2049
|
+
* Options for {@link writeSpecificIntType}.
|
|
2050
|
+
*/
|
|
2051
|
+
export interface WriteSpecificIntTypeOptions {
|
|
2052
|
+
/**
|
|
2053
|
+
* Whether to wrap the value if it is out of range.
|
|
2054
|
+
*
|
|
2055
|
+
* If `false`, an error will be thrown if the value is out of range.
|
|
2056
|
+
*
|
|
2057
|
+
* @default false
|
|
2058
|
+
*/
|
|
2059
|
+
wrap?: boolean;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
/**
|
|
2063
|
+
* Writes an integer to a buffer.
|
|
2064
|
+
*
|
|
2065
|
+
* @template TArrayBuffer The type of the buffer.
|
|
2066
|
+
* @param buffer The buffer to write to.
|
|
2067
|
+
* @param value The integer to write.
|
|
2068
|
+
* @param bytes The number of bytes to write.
|
|
2069
|
+
* @param format The endianness of the data.
|
|
2070
|
+
* @param signed The signedness of the data. Defaults to `false`.
|
|
2071
|
+
* @param offset The offset to write to. Defaults to `0`.
|
|
2072
|
+
* @param options The options to use.
|
|
2073
|
+
* @returns The buffer from the {@link buffer} parameter.
|
|
2074
|
+
*
|
|
2075
|
+
* @throws {RangeError} If the byte length is less than 1.
|
|
2076
|
+
* @throws {RangeError} If the buffer does not have enough space at the specified offset.
|
|
2077
|
+
* @throws {RangeError} If the value is out of range and {@link WriteSpecificIntTypeOptions.wrap | options.wrap} is `false`.
|
|
2078
|
+
*/
|
|
2079
|
+
export function writeSpecificIntType<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>(
|
|
2080
|
+
buffer: Buffer<TArrayBuffer>,
|
|
2081
|
+
value: bigint,
|
|
2082
|
+
bytes: number,
|
|
2083
|
+
format: "BE" | "LE",
|
|
2084
|
+
signed: boolean = false,
|
|
2085
|
+
offset: number = 0,
|
|
2086
|
+
options?: WriteSpecificIntTypeOptions
|
|
2087
|
+
): Buffer<TArrayBuffer> {
|
|
2088
|
+
if (bytes < 1) {
|
|
2089
|
+
throw new RangeError("Byte length must be at least 1");
|
|
2090
|
+
}
|
|
2091
|
+
if (offset + bytes > buffer.length) {
|
|
2092
|
+
throw new RangeError("Buffer does not have enough space at the specified offset");
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
const bitSize: bigint = BigInt(bytes * 8);
|
|
2096
|
+
const maxUnsigned: bigint = (1n << bitSize) - 1n;
|
|
2097
|
+
const minSigned: bigint = -(1n << (bitSize - 1n));
|
|
2098
|
+
const maxSigned: bigint = (1n << (bitSize - 1n)) - 1n;
|
|
2099
|
+
|
|
2100
|
+
if (signed) {
|
|
2101
|
+
if (value < minSigned || value > maxSigned) {
|
|
2102
|
+
if (options?.wrap) {
|
|
2103
|
+
value = (value + (1n << bitSize)) % (1n << bitSize);
|
|
2104
|
+
} else {
|
|
2105
|
+
throw new RangeError(`Signed value out of range for ${bytes} bytes`);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
if (value < 0n) {
|
|
2109
|
+
value += 1n << bitSize;
|
|
2110
|
+
}
|
|
2111
|
+
} else {
|
|
2112
|
+
if (value < 0n || value > maxUnsigned) {
|
|
2113
|
+
if (options?.wrap) {
|
|
2114
|
+
value = value % (1n << bitSize);
|
|
2115
|
+
} else {
|
|
2116
|
+
throw new RangeError(`Unsigned value out of range for ${bytes} bytes`);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
for (let i: number = 0; i < bytes; i++) {
|
|
2122
|
+
const shift: bigint = format === "BE" ? BigInt((bytes - 1 - i) * 8) : BigInt(i * 8);
|
|
2123
|
+
buffer[offset + i] = Number((value >> shift) & 0xffn);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
return buffer;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
/**
|
|
2130
|
+
* Sanitizes a filename.
|
|
2131
|
+
*
|
|
2132
|
+
* @param filename The filename to sanitize.
|
|
2133
|
+
* @returns The sanitized filename.
|
|
2134
|
+
*/
|
|
2135
|
+
export function sanitizeFilename(filename: string): string {
|
|
2136
|
+
return filename
|
|
2137
|
+
.replaceAll(fileNameCharacterFilterRegExp, /* (substring: string): string => encodeURIComponent(substring) */ "")
|
|
2138
|
+
.replaceAll(fileNameEncodeCharacterRegExp, (substring: string): string => encodeURIComponent(substring));
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
/**
|
|
2142
|
+
* Sanitizes a display key.
|
|
2143
|
+
*
|
|
2144
|
+
* @param key The key to sanitize.
|
|
2145
|
+
* @returns The sanitized key.
|
|
2146
|
+
*/
|
|
2147
|
+
export function sanitizeDisplayKey(key: string): string {
|
|
2148
|
+
return key.replaceAll(/[^a-zA-Z0-9-_+,-.;=@~/:>?\\]/g, (substring: string): string => encodeURIComponent(substring) /* "" */);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
/**
|
|
2152
|
+
* Converts a chunk block index to an offset from the minimum corner of the chunk.
|
|
2153
|
+
*
|
|
2154
|
+
* @param index The chunk block index.
|
|
2155
|
+
* @returns The offset from the minimum corner of the chunk.
|
|
2156
|
+
*/
|
|
2157
|
+
export function chunkBlockIndexToOffset(index: number): Vector3 {
|
|
2158
|
+
return {
|
|
2159
|
+
x: (index >> 8) & 0xf,
|
|
2160
|
+
y: (index >> 0) & 0xf,
|
|
2161
|
+
z: (index >> 4) & 0xf,
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
/**
|
|
2166
|
+
* Converts an offset from the minimum corner of the chunk to a chunk block index.
|
|
2167
|
+
*
|
|
2168
|
+
* @param offset The offset from the minimum corner of the chunk.
|
|
2169
|
+
* @returns The chunk block index.
|
|
2170
|
+
*/
|
|
2171
|
+
export function offsetToChunkBlockIndex(offset: Vector3): number {
|
|
2172
|
+
return ((offset.x & 0xf) << 8) | (offset.y & 0xf) | ((offset.z & 0xf) << 4);
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
/**
|
|
2176
|
+
* Reads a 32-bit integer value from a Buffer at the given offset (little-endian).
|
|
2177
|
+
*
|
|
2178
|
+
* @param data The Buffer to read from.
|
|
2179
|
+
* @param offset The offset to read from.
|
|
2180
|
+
* @returns The 32-bit integer value.
|
|
2181
|
+
*/
|
|
2182
|
+
export function getInt32Val(data: Buffer, offset: number): number {
|
|
2183
|
+
let retval: number = 0;
|
|
2184
|
+
// need to switch this to union based like the others.
|
|
2185
|
+
for (let i: number = 0; i < 4; i++) {
|
|
2186
|
+
// if I don't do the static cast, the top bit will be sign extended.
|
|
2187
|
+
retval |= data[offset + i]! << (i * 8);
|
|
2188
|
+
}
|
|
2189
|
+
return retval;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
/**
|
|
2193
|
+
* Writes a 32-bit integer value into a Buffer at the given offset (little-endian).
|
|
2194
|
+
*
|
|
2195
|
+
* @param buffer The Buffer to write into.
|
|
2196
|
+
* @param offset The offset to write into.
|
|
2197
|
+
* @param value The 32-bit integer value to write.
|
|
2198
|
+
*/
|
|
2199
|
+
export function setInt32Val(buffer: Buffer, offset: number, value: number): void {
|
|
2200
|
+
for (let i: number = 0; i < 4; i++) {
|
|
2201
|
+
buffer[offset + i] = (value >> (i * 8)) & 0xff;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
/**
|
|
2206
|
+
* Splits a range into smaller ranges of a given size.
|
|
2207
|
+
*
|
|
2208
|
+
* @param param0 The range to split.
|
|
2209
|
+
* @param size The size of each range.
|
|
2210
|
+
* @returns The split ranges.
|
|
2211
|
+
*/
|
|
2212
|
+
export function splitRange([min, max]: [min: number, max: number], size: number): [from: number, to: number][] {
|
|
2213
|
+
const result: [from: number, to: number][] = [];
|
|
2214
|
+
let start: number = min;
|
|
2215
|
+
|
|
2216
|
+
while (start <= max) {
|
|
2217
|
+
const end: number = Math.min(start + size - 1, max);
|
|
2218
|
+
result.push([start, end]);
|
|
2219
|
+
start = end + 1;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
return result;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
/**
|
|
2226
|
+
* Packs block indices into the buffer using the same scheme as the read loop.
|
|
2227
|
+
*
|
|
2228
|
+
* @param buffer The buffer to write into.
|
|
2229
|
+
* @param blockDataOffset The offset where block data begins in the buffer.
|
|
2230
|
+
* @param block_indices The list of block indices to pack.
|
|
2231
|
+
* @param bitsPerBlock The number of bits used per block.
|
|
2232
|
+
* @param blocksPerWord How many blocks fit inside one 32-bit integer.
|
|
2233
|
+
*/
|
|
2234
|
+
export function writeBlockIndices(buffer: Buffer, blockDataOffset: number, block_indices: number[], bitsPerBlock: number, blocksPerWord: number): void {
|
|
2235
|
+
const wordCount: number = Math.ceil(block_indices.length / blocksPerWord);
|
|
2236
|
+
|
|
2237
|
+
for (let wordIndex: number = 0; wordIndex < wordCount; wordIndex++) {
|
|
2238
|
+
let maskVal: number = 0;
|
|
2239
|
+
|
|
2240
|
+
for (let j: number = 0; j < blocksPerWord; j++) {
|
|
2241
|
+
const blockIndex: number = wordIndex * blocksPerWord + j;
|
|
2242
|
+
if (blockIndex >= block_indices.length) break;
|
|
2243
|
+
|
|
2244
|
+
const blockVal: number = block_indices[blockIndex]!;
|
|
2245
|
+
const shiftAmount: number = j * bitsPerBlock;
|
|
2246
|
+
maskVal |= blockVal /* & ((1 << bitsPerBlock) - 1) */ << shiftAmount;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
setInt32Val(buffer, blockDataOffset + wordIndex * 4, maskVal);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
/**
|
|
2254
|
+
* Gets the chunk indices from a LevelDB key.
|
|
2255
|
+
*
|
|
2256
|
+
* The key must be a [chunk key](https://minecraft.wiki/w/Bedrock_Edition_level_format#Chunk_key_format).
|
|
2257
|
+
*
|
|
2258
|
+
* @param key The key to get the chunk indices for, as a Buffer.
|
|
2259
|
+
* @returns The chunk indices.
|
|
2260
|
+
*/
|
|
2261
|
+
export function getChunkKeyIndices(key: Buffer): SubChunkIndexDimensionVectorXZ | DimensionVectorXZ {
|
|
2262
|
+
return {
|
|
2263
|
+
x: getInt32Val(key, 0),
|
|
2264
|
+
z: getInt32Val(key, 4),
|
|
2265
|
+
dimension: [13, 14].includes(key.length) ? (dimensions[getInt32Val(key, 8)] ?? "overworld") : "overworld",
|
|
2266
|
+
...([10, 14].includes(key.length) ? { subChunkIndex: (key.at(-1)! << 24) >> 24 } : undefined),
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
/**
|
|
2271
|
+
* Generates a raw chunk key from chunk indices.
|
|
2272
|
+
*
|
|
2273
|
+
* @param indices The chunk indices.
|
|
2274
|
+
* @param chunkKeyType The chunk key type.
|
|
2275
|
+
* @returns The raw chunk key.
|
|
2276
|
+
*/
|
|
2277
|
+
export function generateChunkKeyFromIndices(
|
|
2278
|
+
indices: SubChunkIndexDimensionVectorXZ | DimensionVectorXZ,
|
|
2279
|
+
chunkKeyType: DBChunkKeyEntryContentType
|
|
2280
|
+
): Buffer<ArrayBuffer> {
|
|
2281
|
+
const buffer: Buffer<ArrayBuffer> = Buffer.alloc(
|
|
2282
|
+
(indices.dimension === "overworld" ? 9 : 13) + +("subChunkIndex" in indices && indices.subChunkIndex !== undefined)
|
|
2283
|
+
);
|
|
2284
|
+
setInt32Val(buffer, 0, indices.x);
|
|
2285
|
+
setInt32Val(buffer, 4, indices.z);
|
|
2286
|
+
if (indices.dimension !== "overworld") setInt32Val(buffer, 8, dimensions.indexOf(indices.dimension) ?? 0);
|
|
2287
|
+
buffer[8 + +(indices.dimension !== "overworld") * 4] = getIntFromChunkKeyType(chunkKeyType);
|
|
2288
|
+
if ("subChunkIndex" in indices && indices.subChunkIndex !== undefined) buffer[9 + +(indices.dimension !== "overworld") * 4] = indices.subChunkIndex;
|
|
2289
|
+
return buffer;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
/**
|
|
2293
|
+
* Converts a chunk key type to the integer value that represents it.
|
|
2294
|
+
*
|
|
2295
|
+
* @param chunkKeyType The chunk key type.
|
|
2296
|
+
* @returns The integer value.
|
|
2297
|
+
*/
|
|
2298
|
+
function getIntFromChunkKeyType(chunkKeyType: DBChunkKeyEntryContentType): number {
|
|
2299
|
+
switch (chunkKeyType) {
|
|
2300
|
+
case "Data3D":
|
|
2301
|
+
return 0x2b;
|
|
2302
|
+
case "Version":
|
|
2303
|
+
return 0x2c;
|
|
2304
|
+
case "Data2D":
|
|
2305
|
+
return 0x2d;
|
|
2306
|
+
case "Data2DLegacy":
|
|
2307
|
+
return 0x2e;
|
|
2308
|
+
case "SubChunkPrefix":
|
|
2309
|
+
return 0x2f;
|
|
2310
|
+
case "LegacyTerrain":
|
|
2311
|
+
return 0x30;
|
|
2312
|
+
case "BlockEntity":
|
|
2313
|
+
return 0x31;
|
|
2314
|
+
case "Entity":
|
|
2315
|
+
return 0x32;
|
|
2316
|
+
case "PendingTicks":
|
|
2317
|
+
return 0x33;
|
|
2318
|
+
case "LegacyBlockExtraData":
|
|
2319
|
+
return 0x34;
|
|
2320
|
+
case "BiomeState":
|
|
2321
|
+
return 0x35;
|
|
2322
|
+
case "FinalizedState":
|
|
2323
|
+
return 0x36;
|
|
2324
|
+
case "ConversionData":
|
|
2325
|
+
return 0x37;
|
|
2326
|
+
case "BorderBlocks":
|
|
2327
|
+
return 0x38;
|
|
2328
|
+
case "HardcodedSpawners":
|
|
2329
|
+
return 0x39;
|
|
2330
|
+
case "RandomTicks":
|
|
2331
|
+
return 0x3a;
|
|
2332
|
+
case "Checksums":
|
|
2333
|
+
return 0x3b;
|
|
2334
|
+
case "MetaDataHash":
|
|
2335
|
+
return 0x3d;
|
|
2336
|
+
case "GeneratedPreCavesAndCliffsBlending":
|
|
2337
|
+
return 0x3e;
|
|
2338
|
+
case "BlendingBiomeHeight":
|
|
2339
|
+
return 0x3f;
|
|
2340
|
+
case "BlendingData":
|
|
2341
|
+
return 0x40;
|
|
2342
|
+
case "ActorDigestVersion":
|
|
2343
|
+
return 0x41;
|
|
2344
|
+
case "LegacyVersion":
|
|
2345
|
+
return 0x76;
|
|
2346
|
+
case "AABBVolumes":
|
|
2347
|
+
return 0x77;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/**
|
|
2352
|
+
* Gets a human-readable version of a LevelDB key.
|
|
2353
|
+
*
|
|
2354
|
+
* @param key The key to get the display name for, as a Buffer.
|
|
2355
|
+
* @returns A human-readable version of the key.
|
|
2356
|
+
*/
|
|
2357
|
+
export function getKeyDisplayName(key: Buffer): string {
|
|
2358
|
+
const contentType: DBEntryContentType = getContentTypeFromDBKey(key);
|
|
2359
|
+
switch (contentType) {
|
|
2360
|
+
case "Data3D":
|
|
2361
|
+
case "Version":
|
|
2362
|
+
case "Data2D":
|
|
2363
|
+
case "Data2DLegacy":
|
|
2364
|
+
case "SubChunkPrefix":
|
|
2365
|
+
case "LegacyTerrain":
|
|
2366
|
+
case "BlockEntity":
|
|
2367
|
+
case "Entity":
|
|
2368
|
+
case "PendingTicks":
|
|
2369
|
+
case "LegacyBlockExtraData":
|
|
2370
|
+
case "BiomeState":
|
|
2371
|
+
case "FinalizedState":
|
|
2372
|
+
case "ConversionData":
|
|
2373
|
+
case "BorderBlocks":
|
|
2374
|
+
case "HardcodedSpawners":
|
|
2375
|
+
case "RandomTicks":
|
|
2376
|
+
case "Checksums":
|
|
2377
|
+
case "MetaDataHash":
|
|
2378
|
+
case "GeneratedPreCavesAndCliffsBlending":
|
|
2379
|
+
case "BlendingBiomeHeight":
|
|
2380
|
+
case "BlendingData":
|
|
2381
|
+
case "ActorDigestVersion":
|
|
2382
|
+
case "LegacyVersion":
|
|
2383
|
+
case "AABBVolumes": {
|
|
2384
|
+
const indices: SubChunkIndexDimensionVectorXZ | DimensionVectorXZ = getChunkKeyIndices(key);
|
|
2385
|
+
return `${indices.dimension}_${indices.x}_${indices.z}${
|
|
2386
|
+
"subChunkIndex" in indices && indices.subChunkIndex !== undefined ? `_${indices.subChunkIndex}` : ""
|
|
2387
|
+
}_${contentType}`;
|
|
2388
|
+
}
|
|
2389
|
+
case "Digest": {
|
|
2390
|
+
const indices: DimensionVectorXZ = getChunkKeyIndices(key.subarray(4));
|
|
2391
|
+
return `digp_${indices.dimension}_${indices.x}_${indices.z}`;
|
|
2392
|
+
}
|
|
2393
|
+
case "ActorPrefix": {
|
|
2394
|
+
return `actorprefix_${getInt32Val(key, key.length - 8)}_${getInt32Val(key, key.length - 4)}`;
|
|
2395
|
+
}
|
|
2396
|
+
case "AutonomousEntities":
|
|
2397
|
+
case "BiomeData":
|
|
2398
|
+
case "ChunkLoadedRequest":
|
|
2399
|
+
case "Dimension":
|
|
2400
|
+
case "DynamicProperties":
|
|
2401
|
+
case "FlatWorldLayers":
|
|
2402
|
+
case "ForcedWorldCorruption":
|
|
2403
|
+
case "LevelChunkMetaDataDictionary":
|
|
2404
|
+
case "LevelDat":
|
|
2405
|
+
case "Map":
|
|
2406
|
+
case "MobEvents":
|
|
2407
|
+
case "Player":
|
|
2408
|
+
case "PlayerClient":
|
|
2409
|
+
case "Portals":
|
|
2410
|
+
case "RealmsStoriesData":
|
|
2411
|
+
case "SchedulerWT":
|
|
2412
|
+
case "Scoreboard":
|
|
2413
|
+
case "StructureTemplate":
|
|
2414
|
+
case "TickingArea":
|
|
2415
|
+
case "VillageDwellers":
|
|
2416
|
+
case "VillageInfo":
|
|
2417
|
+
case "VillagePOI":
|
|
2418
|
+
case "VillagePlayers":
|
|
2419
|
+
case "Unknown":
|
|
2420
|
+
default:
|
|
2421
|
+
return key.toString("binary");
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
/**
|
|
2426
|
+
* Gets the content type of a LevelDB key.
|
|
2427
|
+
*
|
|
2428
|
+
* @param key The key to get the content type for, as a Buffer.
|
|
2429
|
+
* @returns The content type of the key.
|
|
2430
|
+
*/
|
|
2431
|
+
export function getContentTypeFromDBKey(key: Buffer): DBEntryContentType {
|
|
2432
|
+
if ([9, 10, 13, 14].includes(key.length)) {
|
|
2433
|
+
switch (key.at([10, 14].includes(key.length) ? -2 : -1)) {
|
|
2434
|
+
case 0x2b:
|
|
2435
|
+
return "Data3D";
|
|
2436
|
+
case 0x2c:
|
|
2437
|
+
return "Version";
|
|
2438
|
+
case 0x2d:
|
|
2439
|
+
return "Data2D";
|
|
2440
|
+
case 0x2e:
|
|
2441
|
+
return "Data2DLegacy";
|
|
2442
|
+
case 0x2f:
|
|
2443
|
+
return "SubChunkPrefix";
|
|
2444
|
+
case 0x30:
|
|
2445
|
+
return "LegacyTerrain";
|
|
2446
|
+
case 0x31:
|
|
2447
|
+
return "BlockEntity";
|
|
2448
|
+
case 0x32:
|
|
2449
|
+
return "Entity";
|
|
2450
|
+
case 0x33:
|
|
2451
|
+
return "PendingTicks";
|
|
2452
|
+
case 0x34:
|
|
2453
|
+
return "LegacyBlockExtraData";
|
|
2454
|
+
case 0x35:
|
|
2455
|
+
return "BiomeState";
|
|
2456
|
+
case 0x36:
|
|
2457
|
+
return "FinalizedState";
|
|
2458
|
+
case 0x37:
|
|
2459
|
+
return "ConversionData";
|
|
2460
|
+
case 0x38:
|
|
2461
|
+
return "BorderBlocks";
|
|
2462
|
+
case 0x39:
|
|
2463
|
+
return "HardcodedSpawners";
|
|
2464
|
+
case 0x3a:
|
|
2465
|
+
return "RandomTicks";
|
|
2466
|
+
case 0x3b:
|
|
2467
|
+
return "Checksums";
|
|
2468
|
+
case 0x3d:
|
|
2469
|
+
return "MetaDataHash";
|
|
2470
|
+
case 0x3e:
|
|
2471
|
+
return "GeneratedPreCavesAndCliffsBlending";
|
|
2472
|
+
case 0x3f:
|
|
2473
|
+
return "BlendingBiomeHeight";
|
|
2474
|
+
case 0x40:
|
|
2475
|
+
return "BlendingData";
|
|
2476
|
+
case 0x41:
|
|
2477
|
+
return "ActorDigestVersion";
|
|
2478
|
+
case 0x76:
|
|
2479
|
+
return "LegacyVersion";
|
|
2480
|
+
case 0x77:
|
|
2481
|
+
return "AABBVolumes";
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
const stringKey: string = key.toString();
|
|
2485
|
+
switch (stringKey) {
|
|
2486
|
+
case "~local_player":
|
|
2487
|
+
return "Player";
|
|
2488
|
+
case "game_flatworldlayers":
|
|
2489
|
+
return "FlatWorldLayers";
|
|
2490
|
+
case "Overworld":
|
|
2491
|
+
case "Nether":
|
|
2492
|
+
case "TheEnd":
|
|
2493
|
+
return "Dimension";
|
|
2494
|
+
case "mobevents":
|
|
2495
|
+
return "MobEvents";
|
|
2496
|
+
case "BiomeData":
|
|
2497
|
+
return "BiomeData";
|
|
2498
|
+
case "AutonomousEntities":
|
|
2499
|
+
return "AutonomousEntities";
|
|
2500
|
+
case "scoreboard":
|
|
2501
|
+
return "Scoreboard";
|
|
2502
|
+
case "schedulerWT":
|
|
2503
|
+
return "SchedulerWT";
|
|
2504
|
+
case "portals":
|
|
2505
|
+
return "Portals";
|
|
2506
|
+
case "DynamicProperties":
|
|
2507
|
+
return "DynamicProperties";
|
|
2508
|
+
case "LevelChunkMetaDataDictionary":
|
|
2509
|
+
return "LevelChunkMetaDataDictionary";
|
|
2510
|
+
case "SST_SALOG":
|
|
2511
|
+
case "SST_WORD":
|
|
2512
|
+
case "SST_WORD_":
|
|
2513
|
+
case "DedicatedServerForcedCorruption":
|
|
2514
|
+
return "ForcedWorldCorruption";
|
|
2515
|
+
}
|
|
2516
|
+
switch (true) {
|
|
2517
|
+
case stringKey.startsWith("actorprefix"):
|
|
2518
|
+
return "ActorPrefix";
|
|
2519
|
+
case stringKey.startsWith("structuretemplate"):
|
|
2520
|
+
return "StructureTemplate";
|
|
2521
|
+
case stringKey.startsWith("tickingarea"):
|
|
2522
|
+
return "TickingArea";
|
|
2523
|
+
case stringKey.startsWith("portals"):
|
|
2524
|
+
return "Portals";
|
|
2525
|
+
case stringKey.startsWith("map_"):
|
|
2526
|
+
return "Map";
|
|
2527
|
+
case stringKey.startsWith("player_server_"):
|
|
2528
|
+
return "Player";
|
|
2529
|
+
case stringKey.startsWith("player_"):
|
|
2530
|
+
return "PlayerClient";
|
|
2531
|
+
case stringKey.startsWith("digp"):
|
|
2532
|
+
return "Digest";
|
|
2533
|
+
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):
|
|
2534
|
+
return "ChunkLoadedRequest";
|
|
2535
|
+
case stringKey.startsWith("RealmsStoriesData_"):
|
|
2536
|
+
return "RealmsStoriesData";
|
|
2537
|
+
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):
|
|
2538
|
+
return "VillagePOI";
|
|
2539
|
+
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):
|
|
2540
|
+
return "VillageInfo";
|
|
2541
|
+
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):
|
|
2542
|
+
return "VillageDwellers";
|
|
2543
|
+
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):
|
|
2544
|
+
return "VillagePlayers";
|
|
2545
|
+
}
|
|
2546
|
+
return "Unknown";
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* Gets the biome type from its ID.
|
|
2551
|
+
*
|
|
2552
|
+
* @param id The ID of the biome.
|
|
2553
|
+
* @returns The biome type.
|
|
2554
|
+
*/
|
|
2555
|
+
export function getBiomeTypeFromID(id: number): keyof (typeof BiomeData)["int_map"] | undefined {
|
|
2556
|
+
return (Object.keys(BiomeData.int_map) as (keyof (typeof BiomeData)["int_map"])[]).find(
|
|
2557
|
+
(key: keyof (typeof BiomeData)["int_map"]): boolean => BiomeData.int_map[key] === id
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
/**
|
|
2562
|
+
* Gets the biome ID from its type.
|
|
2563
|
+
*
|
|
2564
|
+
* @param type The type of the biome.
|
|
2565
|
+
* @returns The biome ID.
|
|
2566
|
+
*/
|
|
2567
|
+
export function getBiomeIDFromType(type: keyof (typeof BiomeData)["int_map"]): number | undefined {
|
|
2568
|
+
return BiomeData.int_map[type];
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
//#endregion
|
|
2572
|
+
|
|
2573
|
+
// --------------------------------------------------------------------------------
|
|
2574
|
+
// Classes
|
|
2575
|
+
// --------------------------------------------------------------------------------
|
|
2576
|
+
|
|
2577
|
+
//#region Classes
|
|
2578
|
+
|
|
2579
|
+
/**
|
|
2580
|
+
* @todo
|
|
2581
|
+
*/
|
|
2582
|
+
export class Structure {
|
|
2583
|
+
/**
|
|
2584
|
+
* @todo
|
|
2585
|
+
*/
|
|
2586
|
+
public target?:
|
|
2587
|
+
| {
|
|
2588
|
+
/**
|
|
2589
|
+
* The type of the target structure data.
|
|
2590
|
+
*/
|
|
2591
|
+
type: "LevelDBEntry";
|
|
2592
|
+
/**
|
|
2593
|
+
* The LevelDB containing the target structure data.
|
|
2594
|
+
*/
|
|
2595
|
+
db: LevelDB;
|
|
2596
|
+
/**
|
|
2597
|
+
* The key of the target structure data in the LevelDB.
|
|
2598
|
+
*/
|
|
2599
|
+
key: Buffer;
|
|
2600
|
+
}
|
|
2601
|
+
| {
|
|
2602
|
+
/**
|
|
2603
|
+
* The type of the target structure data.
|
|
2604
|
+
*/
|
|
2605
|
+
type: "File";
|
|
2606
|
+
/**
|
|
2607
|
+
* The absolute path to the target structure data.
|
|
2608
|
+
*/
|
|
2609
|
+
path: string;
|
|
2610
|
+
}
|
|
2611
|
+
| undefined;
|
|
2612
|
+
/**
|
|
2613
|
+
* @todo
|
|
2614
|
+
*/
|
|
2615
|
+
public constructor(options: { target?: Structure["target"] }) {
|
|
2616
|
+
this.target = options.target;
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* @todo
|
|
2620
|
+
*/
|
|
2621
|
+
public fillBlocks(from: Vector3, to: Vector3, block: NBTSchemas.NBTSchemaTypes.Block): any {
|
|
2622
|
+
throw new Error("Method not implemented.");
|
|
2623
|
+
}
|
|
2624
|
+
/**
|
|
2625
|
+
* @todo
|
|
2626
|
+
*/
|
|
2627
|
+
public saveChanges(): any {
|
|
2628
|
+
throw new Error("Method not implemented.");
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* @todo
|
|
2632
|
+
*/
|
|
2633
|
+
public delete(): any {
|
|
2634
|
+
throw new Error("Method not implemented.");
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* @todo
|
|
2638
|
+
*/
|
|
2639
|
+
public expand(min: Vector3, max: Vector3): any {
|
|
2640
|
+
throw new Error("Method not implemented.");
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* @todo
|
|
2644
|
+
*/
|
|
2645
|
+
public shrink(min: Vector3, max: Vector3): any {
|
|
2646
|
+
throw new Error("Method not implemented.");
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* @todo
|
|
2650
|
+
*/
|
|
2651
|
+
public move(min: Vector3, max: Vector3): any {
|
|
2652
|
+
throw new Error("Method not implemented.");
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* @todo
|
|
2656
|
+
*/
|
|
2657
|
+
public scale(scale: Vector3 | number): any {
|
|
2658
|
+
throw new Error("Method not implemented.");
|
|
2659
|
+
}
|
|
2660
|
+
/**
|
|
2661
|
+
* @todo
|
|
2662
|
+
*/
|
|
2663
|
+
public rotate(angle: 0 | 90 | 180 | 270, axis: "x" | "y" | "z" = "y"): any {
|
|
2664
|
+
throw new Error("Method not implemented.");
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* @todo
|
|
2668
|
+
*/
|
|
2669
|
+
public mirror(axis: "x" | "y" | "z"): any {
|
|
2670
|
+
throw new Error("Method not implemented.");
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* @todo
|
|
2674
|
+
*/
|
|
2675
|
+
public clear(): any {
|
|
2676
|
+
throw new Error("Method not implemented.");
|
|
2677
|
+
}
|
|
2678
|
+
/**
|
|
2679
|
+
* @todo
|
|
2680
|
+
*/
|
|
2681
|
+
public clearSectionData(from: Vector3, to: Vector3): any {
|
|
2682
|
+
throw new Error("Method not implemented.");
|
|
2683
|
+
}
|
|
2684
|
+
/**
|
|
2685
|
+
* @todo
|
|
2686
|
+
*/
|
|
2687
|
+
public getSectionData(from: Vector3, to: Vector3): StructureSectionData {
|
|
2688
|
+
throw new Error("Method not implemented.");
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* @todo
|
|
2692
|
+
*/
|
|
2693
|
+
public replaceSectionData(offset: Vector3, data: StructureSectionData, options: StructureReplaceSectionDataOptions = {}): any {
|
|
2694
|
+
throw new Error("Method not implemented.");
|
|
2695
|
+
}
|
|
2696
|
+
/**
|
|
2697
|
+
* @todo
|
|
2698
|
+
*/
|
|
2699
|
+
public exportPrismarineNBT(): NBTSchemas.NBTSchemaTypes.StructureTemplate {
|
|
2700
|
+
throw new Error("Method not implemented.");
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* Options for {@link Structure.replaceSectionData}.
|
|
2706
|
+
*/
|
|
2707
|
+
export interface StructureReplaceSectionDataOptions {
|
|
2708
|
+
/**
|
|
2709
|
+
* Whether to automatically expand the structure to fit the data if necessary, instead of cropping it.
|
|
2710
|
+
*
|
|
2711
|
+
* @default false
|
|
2712
|
+
*/
|
|
2713
|
+
autoExpand?: boolean;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
//#endregion
|