mca-json 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +14 -0
- package/.prettierrc +4 -0
- package/README.md +373 -0
- package/dist/bin/mca-chunks.d.ts +3 -0
- package/dist/bin/mca-chunks.d.ts.map +1 -0
- package/dist/bin/mca-chunks.js +43 -0
- package/dist/bin/mca-chunks.js.map +1 -0
- package/dist/bin/mca-find-chunks-with-signs.d.ts +3 -0
- package/dist/bin/mca-find-chunks-with-signs.d.ts.map +1 -0
- package/dist/bin/mca-find-chunks-with-signs.js +79 -0
- package/dist/bin/mca-find-chunks-with-signs.js.map +1 -0
- package/dist/bin/mca-json.d.ts +3 -0
- package/dist/bin/mca-json.d.ts.map +1 -0
- package/dist/bin/mca-json.js +73 -0
- package/dist/bin/mca-json.js.map +1 -0
- package/dist/bin/mca-trim-chunks-without-signs.d.ts +3 -0
- package/dist/bin/mca-trim-chunks-without-signs.d.ts.map +1 -0
- package/dist/bin/mca-trim-chunks-without-signs.js +117 -0
- package/dist/bin/mca-trim-chunks-without-signs.js.map +1 -0
- package/dist/bin/nbt-get-player-location.d.ts +3 -0
- package/dist/bin/nbt-get-player-location.d.ts.map +1 -0
- package/dist/bin/nbt-get-player-location.js +83 -0
- package/dist/bin/nbt-get-player-location.js.map +1 -0
- package/dist/bin/nbt-json.d.ts +3 -0
- package/dist/bin/nbt-json.d.ts.map +1 -0
- package/dist/bin/nbt-json.js +64 -0
- package/dist/bin/nbt-json.js.map +1 -0
- package/dist/block/banner.d.ts +13 -0
- package/dist/block/banner.d.ts.map +1 -0
- package/dist/block/banner.js +28 -0
- package/dist/block/banner.js.map +1 -0
- package/dist/block/barrel.d.ts +17 -0
- package/dist/block/barrel.d.ts.map +1 -0
- package/dist/block/barrel.js +21 -0
- package/dist/block/barrel.js.map +1 -0
- package/dist/block/beacon.d.ts +14 -0
- package/dist/block/beacon.d.ts.map +1 -0
- package/dist/block/beacon.js +14 -0
- package/dist/block/beacon.js.map +1 -0
- package/dist/block/bed.d.ts +4 -0
- package/dist/block/bed.d.ts.map +1 -0
- package/dist/block/bed.js +4 -0
- package/dist/block/bed.js.map +1 -0
- package/dist/block/beehive.d.ts +9 -0
- package/dist/block/beehive.d.ts.map +1 -0
- package/dist/block/beehive.js +10 -0
- package/dist/block/beehive.js.map +1 -0
- package/dist/block/bell.d.ts +4 -0
- package/dist/block/bell.d.ts.map +1 -0
- package/dist/block/bell.js +4 -0
- package/dist/block/bell.js.map +1 -0
- package/dist/block/block.d.ts +23 -0
- package/dist/block/block.d.ts.map +1 -0
- package/dist/block/block.js +49 -0
- package/dist/block/block.js.map +1 -0
- package/dist/block/generic.d.ts +10 -0
- package/dist/block/generic.d.ts.map +1 -0
- package/dist/block/generic.js +14 -0
- package/dist/block/generic.js.map +1 -0
- package/dist/block/has-entity-data.d.ts +8 -0
- package/dist/block/has-entity-data.d.ts.map +1 -0
- package/dist/block/has-entity-data.js +12 -0
- package/dist/block/has-entity-data.js.map +1 -0
- package/dist/block/mixins/custom-name.d.ts +5 -0
- package/dist/block/mixins/custom-name.d.ts.map +1 -0
- package/dist/block/mixins/custom-name.js +7 -0
- package/dist/block/mixins/custom-name.js.map +1 -0
- package/dist/block/mixins/lock.d.ts +5 -0
- package/dist/block/mixins/lock.d.ts.map +1 -0
- package/dist/block/mixins/lock.js +7 -0
- package/dist/block/mixins/lock.js.map +1 -0
- package/dist/block/mixins/mixin.d.ts +27 -0
- package/dist/block/mixins/mixin.d.ts.map +1 -0
- package/dist/block/mixins/mixin.js +36 -0
- package/dist/block/mixins/mixin.js.map +1 -0
- package/dist/block/sign.d.ts +15 -0
- package/dist/block/sign.d.ts.map +1 -0
- package/dist/block/sign.js +91 -0
- package/dist/block/sign.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/anvil.d.ts +54 -0
- package/dist/lib/anvil.d.ts.map +1 -0
- package/dist/lib/anvil.js +141 -0
- package/dist/lib/anvil.js.map +1 -0
- package/dist/lib/binary-data.d.ts +48 -0
- package/dist/lib/binary-data.d.ts.map +1 -0
- package/dist/lib/binary-data.js +188 -0
- package/dist/lib/binary-data.js.map +1 -0
- package/dist/lib/bit-data.d.ts +16 -0
- package/dist/lib/bit-data.d.ts.map +1 -0
- package/dist/lib/bit-data.js +75 -0
- package/dist/lib/bit-data.js.map +1 -0
- package/dist/lib/block-data.d.ts +20 -0
- package/dist/lib/block-data.d.ts.map +1 -0
- package/dist/lib/block-data.js +125 -0
- package/dist/lib/block-data.js.map +1 -0
- package/dist/lib/chunk.d.ts +112 -0
- package/dist/lib/chunk.d.ts.map +1 -0
- package/dist/lib/chunk.js +299 -0
- package/dist/lib/chunk.js.map +1 -0
- package/dist/nbt/nbt-base.d.ts +20 -0
- package/dist/nbt/nbt-base.d.ts.map +1 -0
- package/dist/nbt/nbt-base.js +30 -0
- package/dist/nbt/nbt-base.js.map +1 -0
- package/dist/nbt/nbt-byte-array.d.ts +13 -0
- package/dist/nbt/nbt-byte-array.d.ts.map +1 -0
- package/dist/nbt/nbt-byte-array.js +30 -0
- package/dist/nbt/nbt-byte-array.js.map +1 -0
- package/dist/nbt/nbt-byte.d.ts +13 -0
- package/dist/nbt/nbt-byte.d.ts.map +1 -0
- package/dist/nbt/nbt-byte.js +25 -0
- package/dist/nbt/nbt-byte.js.map +1 -0
- package/dist/nbt/nbt-compound.d.ts +15 -0
- package/dist/nbt/nbt-compound.d.ts.map +1 -0
- package/dist/nbt/nbt-compound.js +68 -0
- package/dist/nbt/nbt-compound.js.map +1 -0
- package/dist/nbt/nbt-double.d.ts +13 -0
- package/dist/nbt/nbt-double.d.ts.map +1 -0
- package/dist/nbt/nbt-double.js +25 -0
- package/dist/nbt/nbt-double.js.map +1 -0
- package/dist/nbt/nbt-end.d.ts +11 -0
- package/dist/nbt/nbt-end.d.ts.map +1 -0
- package/dist/nbt/nbt-end.js +22 -0
- package/dist/nbt/nbt-end.js.map +1 -0
- package/dist/nbt/nbt-float.d.ts +13 -0
- package/dist/nbt/nbt-float.d.ts.map +1 -0
- package/dist/nbt/nbt-float.js +25 -0
- package/dist/nbt/nbt-float.js.map +1 -0
- package/dist/nbt/nbt-int-array.d.ts +13 -0
- package/dist/nbt/nbt-int-array.d.ts.map +1 -0
- package/dist/nbt/nbt-int-array.js +30 -0
- package/dist/nbt/nbt-int-array.js.map +1 -0
- package/dist/nbt/nbt-int.d.ts +13 -0
- package/dist/nbt/nbt-int.d.ts.map +1 -0
- package/dist/nbt/nbt-int.js +25 -0
- package/dist/nbt/nbt-int.js.map +1 -0
- package/dist/nbt/nbt-list.d.ts +17 -0
- package/dist/nbt/nbt-list.d.ts.map +1 -0
- package/dist/nbt/nbt-list.js +70 -0
- package/dist/nbt/nbt-list.js.map +1 -0
- package/dist/nbt/nbt-long-array.d.ts +13 -0
- package/dist/nbt/nbt-long-array.d.ts.map +1 -0
- package/dist/nbt/nbt-long-array.js +32 -0
- package/dist/nbt/nbt-long-array.js.map +1 -0
- package/dist/nbt/nbt-long.d.ts +13 -0
- package/dist/nbt/nbt-long.d.ts.map +1 -0
- package/dist/nbt/nbt-long.js +25 -0
- package/dist/nbt/nbt-long.js.map +1 -0
- package/dist/nbt/nbt-short.d.ts +13 -0
- package/dist/nbt/nbt-short.d.ts.map +1 -0
- package/dist/nbt/nbt-short.js +25 -0
- package/dist/nbt/nbt-short.js.map +1 -0
- package/dist/nbt/nbt-string.d.ts +13 -0
- package/dist/nbt/nbt-string.d.ts.map +1 -0
- package/dist/nbt/nbt-string.js +27 -0
- package/dist/nbt/nbt-string.js.map +1 -0
- package/dist/nbt/nbt-tag-type.d.ts +16 -0
- package/dist/nbt/nbt-tag-type.d.ts.map +1 -0
- package/dist/nbt/nbt-tag-type.js +17 -0
- package/dist/nbt/nbt-tag-type.js.map +1 -0
- package/dist/nbt/nbt.d.ts +11 -0
- package/dist/nbt/nbt.d.ts.map +1 -0
- package/dist/nbt/nbt.js +57 -0
- package/dist/nbt/nbt.js.map +1 -0
- package/dist/nbt/snbt-data.d.ts +18 -0
- package/dist/nbt/snbt-data.d.ts.map +1 -0
- package/dist/nbt/snbt-data.js +34 -0
- package/dist/nbt/snbt-data.js.map +1 -0
- package/dist/nbt/snbt-parse.d.ts +3 -0
- package/dist/nbt/snbt-parse.d.ts.map +1 -0
- package/dist/nbt/snbt-parse.js +201 -0
- package/dist/nbt/snbt-parse.js.map +1 -0
- package/dist/nbt/snbt-to-nbt.d.ts +7 -0
- package/dist/nbt/snbt-to-nbt.d.ts.map +1 -0
- package/dist/nbt/snbt-to-nbt.js +178 -0
- package/dist/nbt/snbt-to-nbt.js.map +1 -0
- package/dist/types/coords.d.ts +3 -0
- package/dist/types/coords.d.ts.map +1 -0
- package/dist/types/coords.js +2 -0
- package/dist/types/coords.js.map +1 -0
- package/package.json +28 -0
- package/src/bin/mca-chunks.ts +54 -0
- package/src/bin/mca-find-chunks-with-signs.ts +109 -0
- package/src/bin/mca-json.ts +96 -0
- package/src/bin/mca-trim-chunks-without-signs.ts +146 -0
- package/src/bin/nbt-get-player-location.ts +102 -0
- package/src/bin/nbt-json.ts +85 -0
- package/src/block/banner.ts +50 -0
- package/src/block/barrel.ts +34 -0
- package/src/block/beacon.ts +20 -0
- package/src/block/bed.ts +3 -0
- package/src/block/beehive.ts +17 -0
- package/src/block/bell.ts +3 -0
- package/src/block/block.ts +62 -0
- package/src/block/generic.ts +14 -0
- package/src/block/has-entity-data.ts +20 -0
- package/src/block/mixins/custom-name.ts +8 -0
- package/src/block/mixins/lock.ts +8 -0
- package/src/block/mixins/mixin.ts +53 -0
- package/src/block/sign.ts +121 -0
- package/src/index.ts +38 -0
- package/src/lib/anvil.ts +178 -0
- package/src/lib/binary-data.ts +247 -0
- package/src/lib/bit-data.ts +101 -0
- package/src/lib/block-data.ts +180 -0
- package/src/lib/chunk.ts +389 -0
- package/src/nbt/nbt-base.ts +38 -0
- package/src/nbt/nbt-byte-array.ts +38 -0
- package/src/nbt/nbt-byte.ts +31 -0
- package/src/nbt/nbt-compound.ts +95 -0
- package/src/nbt/nbt-double.ts +31 -0
- package/src/nbt/nbt-end.ts +27 -0
- package/src/nbt/nbt-float.ts +31 -0
- package/src/nbt/nbt-int-array.ts +38 -0
- package/src/nbt/nbt-int.ts +31 -0
- package/src/nbt/nbt-list.ts +103 -0
- package/src/nbt/nbt-long-array.ts +40 -0
- package/src/nbt/nbt-long.ts +31 -0
- package/src/nbt/nbt-short.ts +31 -0
- package/src/nbt/nbt-string.ts +35 -0
- package/src/nbt/nbt-tag-type.ts +15 -0
- package/src/nbt/nbt.ts +70 -0
- package/src/nbt/snbt-data.ts +70 -0
- package/src/nbt/snbt-parse.ts +256 -0
- package/src/nbt/snbt-to-nbt.ts +219 -0
- package/src/types/coords.ts +2 -0
- package/src/types/neodoc.d.ts +28 -0
- package/tsconfig.json +25 -0
package/src/lib/anvil.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { BinaryData } from './binary-data';
|
|
2
|
+
import { Chunk } from './chunk';
|
|
3
|
+
import { Coords2d } from '../types/coords';
|
|
4
|
+
import { inflate } from 'pako';
|
|
5
|
+
import { Nbt } from '../nbt/nbt';
|
|
6
|
+
|
|
7
|
+
type LocationEntry = {
|
|
8
|
+
offset: number; // 3 bytes
|
|
9
|
+
sectorCount: number; // 1 byte
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
enum CompressionType {
|
|
13
|
+
GZIP = 1,
|
|
14
|
+
ZLIB = 2,
|
|
15
|
+
NONE = 3,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const LOCATION_ENTRIES_PER_FILE = 1024;
|
|
19
|
+
const SECTOR_SIZE = 4096;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read and write Minecraft Anvil region files.
|
|
23
|
+
*/
|
|
24
|
+
export class Anvil {
|
|
25
|
+
static fromBuffer(buffer: ArrayBufferLike): Anvil {
|
|
26
|
+
return new Anvil(buffer);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
cachedLocationEntries: LocationEntry[] | null = null;
|
|
30
|
+
data: BinaryData;
|
|
31
|
+
|
|
32
|
+
constructor(data: ArrayBufferLike) {
|
|
33
|
+
this.data = new BinaryData(data);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns the buffer that holds the Anvil data.
|
|
38
|
+
*/
|
|
39
|
+
buffer(): ArrayBufferLike {
|
|
40
|
+
return this.data.buffer();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Delete a chunk. The location is either given in X,Z chunk coordinates or
|
|
45
|
+
* passed in from a Chunk object.
|
|
46
|
+
*/
|
|
47
|
+
deleteChunk(chunkLocation: Coords2d | Chunk): void {
|
|
48
|
+
let index;
|
|
49
|
+
|
|
50
|
+
if (Array.isArray(chunkLocation)) {
|
|
51
|
+
index = this.chunkCoordinatesToIndex(chunkLocation);
|
|
52
|
+
} else {
|
|
53
|
+
const coords = chunkLocation.chunkCoordinates();
|
|
54
|
+
|
|
55
|
+
if (!coords) {
|
|
56
|
+
throw new Error('Chunk does not have coordinates');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
index = this.chunkCoordinatesToIndex(coords);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Clear cache before modifying location entries
|
|
63
|
+
this.cachedLocationEntries = null;
|
|
64
|
+
this.data.seek(index * 4);
|
|
65
|
+
this.data.setNByteInteger(0, 3);
|
|
66
|
+
this.data.setByte(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parses all chunks in the Anvil blob. Chunk objects are read in the order
|
|
71
|
+
* they are stored in the location entry header sector.
|
|
72
|
+
*
|
|
73
|
+
* @returns Chunk[] a list of Chunk objects
|
|
74
|
+
*/
|
|
75
|
+
getAllChunks(): Chunk[] {
|
|
76
|
+
// Empty files can be written
|
|
77
|
+
if (this.data.byteLength() === 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const chunks = this.getLocationEntries()
|
|
82
|
+
.filter((x) => x.sectorCount > 0)
|
|
83
|
+
.map((offset) => {
|
|
84
|
+
const nbt = Nbt.fromBuffer(this.getChunkData(offset.offset));
|
|
85
|
+
|
|
86
|
+
return new Chunk(nbt);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return chunks;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Gets a chunk using chunk coordinates.
|
|
93
|
+
getChunk(chunkCoords: Coords2d): Chunk | undefined {
|
|
94
|
+
const locationEntries = this.getLocationEntries();
|
|
95
|
+
const entry = locationEntries[this.chunkCoordinatesToIndex(chunkCoords)];
|
|
96
|
+
|
|
97
|
+
if (entry?.sectorCount > 0) {
|
|
98
|
+
const nbt = Nbt.fromBuffer(this.getChunkData(entry.offset));
|
|
99
|
+
|
|
100
|
+
return new Chunk(nbt);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Convert from chunk coordinates to region-relative chunk coordinates, and
|
|
108
|
+
* then to an index.
|
|
109
|
+
*/
|
|
110
|
+
private chunkCoordinatesToIndex(chunkCoords: Coords2d): number {
|
|
111
|
+
let regionChunkX = chunkCoords[0];
|
|
112
|
+
let regionChunkZ = chunkCoords[1];
|
|
113
|
+
const step = 4096 * 4096;
|
|
114
|
+
|
|
115
|
+
while (regionChunkX < 0) {
|
|
116
|
+
regionChunkX += step;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
while (regionChunkZ < 0) {
|
|
120
|
+
regionChunkZ += step;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (regionChunkX % 32) + (regionChunkZ % 32) * 32;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Extracts data for a chunk present at the given offset in the Anvil blob.
|
|
128
|
+
* The offset is provided in sectors of SECTOR_SIZE bytes (as in the chunk
|
|
129
|
+
* location entries from the blob's first header sector).
|
|
130
|
+
*
|
|
131
|
+
* @param offset the chunk's offset in number of sectors from the Anvil blob's start.
|
|
132
|
+
*/
|
|
133
|
+
protected getChunkData(offset?: number): ArrayBufferLike {
|
|
134
|
+
if (offset !== undefined) {
|
|
135
|
+
this.data.seek(offset * SECTOR_SIZE);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const length = this.data.getUInt();
|
|
139
|
+
const compressionType = this.data.getByte();
|
|
140
|
+
const data = this.data.getArrayBuffer(length - 1); // First byte is the compression type
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
compressionType === CompressionType.ZLIB ||
|
|
144
|
+
compressionType === CompressionType.GZIP
|
|
145
|
+
) {
|
|
146
|
+
// Could be SharedArrayBuffer, but it won't get used again after
|
|
147
|
+
// inflation, so it being "shared" does not matter.
|
|
148
|
+
return inflate(data as ArrayBuffer).buffer;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return data;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Retrieves the full list of chunk location entries from this Anvil blob.
|
|
156
|
+
* The pointer will be moved to the beginning of the first header sector at
|
|
157
|
+
* the start of this method and advanced to the end during reading.
|
|
158
|
+
*/
|
|
159
|
+
protected getLocationEntries(): LocationEntry[] {
|
|
160
|
+
if (this.cachedLocationEntries) {
|
|
161
|
+
return this.cachedLocationEntries;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.data.seek(0);
|
|
165
|
+
const result: LocationEntry[] = [];
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < LOCATION_ENTRIES_PER_FILE; i += 1) {
|
|
168
|
+
result.push({
|
|
169
|
+
offset: this.data.getNByteInteger(3),
|
|
170
|
+
sectorCount: this.data.getByte(),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.cachedLocationEntries = result;
|
|
175
|
+
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a read/write view of a binary data buffer. Write operations that are
|
|
3
|
+
* beyond the buffer's current size will increase the buffer's size in chunks.
|
|
4
|
+
*/
|
|
5
|
+
export class BinaryData {
|
|
6
|
+
private position = 0;
|
|
7
|
+
private view: DataView;
|
|
8
|
+
|
|
9
|
+
constructor(data: ArrayBufferLike) {
|
|
10
|
+
this.view = new DataView(data);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
buffer(): ArrayBufferLike {
|
|
14
|
+
return this.view.buffer;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
byteLength(): number {
|
|
18
|
+
return this.view.byteLength;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
currentPosition(): number {
|
|
22
|
+
return this.position;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getArrayBuffer(length: number) {
|
|
26
|
+
const data = this.view.buffer.slice(
|
|
27
|
+
this.position,
|
|
28
|
+
this.position + length
|
|
29
|
+
);
|
|
30
|
+
this.position += length;
|
|
31
|
+
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getByte() {
|
|
36
|
+
const value = this.view.getUint8(this.position);
|
|
37
|
+
this.position += 1;
|
|
38
|
+
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getDouble() {
|
|
43
|
+
const value = this.view.getFloat64(this.position);
|
|
44
|
+
this.position += 8;
|
|
45
|
+
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getFloat() {
|
|
50
|
+
const value = this.view.getFloat32(this.position);
|
|
51
|
+
this.position += 4;
|
|
52
|
+
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getInt() {
|
|
57
|
+
const value = this.view.getInt32(this.position);
|
|
58
|
+
this.position += 4;
|
|
59
|
+
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getInt64(): bigint {
|
|
64
|
+
const value = this.view.getBigInt64(this.position, true);
|
|
65
|
+
this.position += 8;
|
|
66
|
+
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getInt64LE(): bigint {
|
|
71
|
+
const value = this.view.getBigInt64(this.position);
|
|
72
|
+
this.position += 8;
|
|
73
|
+
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getNByteInteger(n: number) {
|
|
78
|
+
let value = 0;
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < n; i += 1) {
|
|
81
|
+
const b = this.getByte();
|
|
82
|
+
value = value * 256 + b;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getShort() {
|
|
89
|
+
const value = this.view.getInt16(this.position);
|
|
90
|
+
this.position += 2;
|
|
91
|
+
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getString(len: number) {
|
|
96
|
+
let s = '';
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < len; i += 1) {
|
|
99
|
+
const c = this.getByte();
|
|
100
|
+
|
|
101
|
+
if (c > 0) {
|
|
102
|
+
s += String.fromCharCode(c);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return s;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getUInt() {
|
|
110
|
+
const value = this.view.getUint32(this.position);
|
|
111
|
+
this.position += 4;
|
|
112
|
+
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getUInt64(): bigint {
|
|
117
|
+
const v = this.view.getBigUint64(this.position);
|
|
118
|
+
this.position += 8;
|
|
119
|
+
|
|
120
|
+
return v;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getUInt64LE(): bigint {
|
|
124
|
+
const v = this.view.getBigUint64(this.position, true);
|
|
125
|
+
this.position += 8;
|
|
126
|
+
|
|
127
|
+
return v;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getUShort() {
|
|
131
|
+
const value = this.view.getUint16(this.position);
|
|
132
|
+
this.position += 2;
|
|
133
|
+
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Checks how many bytes are between the current position and the end of
|
|
139
|
+
* the buffer.
|
|
140
|
+
*/
|
|
141
|
+
remainingLength() {
|
|
142
|
+
return this.view.byteLength - this.position;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
seek(offset: number): void {
|
|
146
|
+
this.position = offset;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setArrayBuffer(value: ArrayBuffer) {
|
|
150
|
+
this.growToFit(value.byteLength);
|
|
151
|
+
new Uint8Array(this.view.buffer).set(
|
|
152
|
+
new Uint8Array(value),
|
|
153
|
+
this.position
|
|
154
|
+
);
|
|
155
|
+
this.position += value.byteLength;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setByte(value: number) {
|
|
159
|
+
this.growToFit(1);
|
|
160
|
+
this.view.setUint8(this.position, value);
|
|
161
|
+
this.position += 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
setDouble(value: number) {
|
|
165
|
+
this.growToFit(8);
|
|
166
|
+
this.view.setFloat64(this.position, value);
|
|
167
|
+
this.position += 8;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setFloat(value: number) {
|
|
171
|
+
this.growToFit(4);
|
|
172
|
+
this.view.setFloat32(this.position, value);
|
|
173
|
+
this.position += 4;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
setInt(value: number) {
|
|
177
|
+
this.growToFit(4);
|
|
178
|
+
this.view.setInt32(this.position, value);
|
|
179
|
+
this.position += 4;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
setInt64(value: bigint) {
|
|
183
|
+
this.growToFit(8);
|
|
184
|
+
this.view.setBigInt64(this.position, value);
|
|
185
|
+
this.position += 8;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
setInt64LE(value: bigint) {
|
|
189
|
+
this.growToFit(8);
|
|
190
|
+
this.view.setBigUint64(this.position, value, true);
|
|
191
|
+
this.position += 8;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
setNByteInteger(value: number, size: number) {
|
|
195
|
+
this.growToFit(size);
|
|
196
|
+
|
|
197
|
+
for (let i = size - 1; i >= 0; i -= 1) {
|
|
198
|
+
this.setByte(Math.floor(value / Math.pow(256, i)) % 256);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
setShort(value: number) {
|
|
203
|
+
this.growToFit(2);
|
|
204
|
+
this.view.setInt16(this.position, value);
|
|
205
|
+
this.position += 2;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
setString(value: string) {
|
|
209
|
+
this.growToFit(value.length);
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
212
|
+
this.setByte(value.charCodeAt(i));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
setUInt(value: number) {
|
|
217
|
+
this.growToFit(4);
|
|
218
|
+
this.view.setUint32(this.position, value);
|
|
219
|
+
this.position += 4;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
setUInt64(value: bigint) {
|
|
223
|
+
this.growToFit(8);
|
|
224
|
+
this.view.setBigUint64(this.position, value);
|
|
225
|
+
this.position += 8;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
setUInt64LE(value: bigint) {
|
|
229
|
+
this.growToFit(8);
|
|
230
|
+
this.view.setBigUint64(this.position, value, true);
|
|
231
|
+
this.position += 8;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setUShort(value: number) {
|
|
235
|
+
this.growToFit(2);
|
|
236
|
+
this.view.setUint16(this.position, value);
|
|
237
|
+
this.position += 2;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private growToFit(size: number) {
|
|
241
|
+
while (size > this.remainingLength()) {
|
|
242
|
+
const newBuffer = new ArrayBuffer(this.view.byteLength * 2);
|
|
243
|
+
new Uint8Array(newBuffer).set(new Uint8Array(this.view.buffer));
|
|
244
|
+
this.view = new DataView(newBuffer);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { NbtLongArray } from '../nbt/nbt-long-array';
|
|
3
|
+
|
|
4
|
+
const debugLog = debug('bit-data');
|
|
5
|
+
|
|
6
|
+
// At most, we will have 7 bits to mask
|
|
7
|
+
const MASK = [0, 1, 3, 7, 15, 31, 63, 127];
|
|
8
|
+
|
|
9
|
+
export class BitData {
|
|
10
|
+
static fromLongArrayTag(tag: NbtLongArray): BitData {
|
|
11
|
+
const arrayBuffer = new ArrayBuffer(tag.data.length * 8);
|
|
12
|
+
const view = new DataView(arrayBuffer);
|
|
13
|
+
let position = 0;
|
|
14
|
+
|
|
15
|
+
for (const value of tag.data) {
|
|
16
|
+
// Convert back to a byte stream. The source comes from
|
|
17
|
+
// NbtLongArray.fromBinaryData(). Make sure the endianness is
|
|
18
|
+
// correct and matches the reader.
|
|
19
|
+
view.setBigInt64(position, value, true);
|
|
20
|
+
position += 8;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return new BitData(arrayBuffer);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private length: number;
|
|
27
|
+
private partial = 0;
|
|
28
|
+
private partialCount = 0;
|
|
29
|
+
private position = 0;
|
|
30
|
+
private view: DataView;
|
|
31
|
+
|
|
32
|
+
constructor(data: ArrayBuffer) {
|
|
33
|
+
this.view = new DataView(data);
|
|
34
|
+
this.length = data.byteLength;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
currentPosition() {
|
|
38
|
+
return this.position;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getBits(n: number): number {
|
|
42
|
+
debugLog(
|
|
43
|
+
`getBits(${n}), start, position ${this.position}, bits ${this.partial.toString(2).padStart(this.partialCount, '0').substr(-this.partialCount)}`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Add extra bits to the left
|
|
47
|
+
while (n > this.partialCount) {
|
|
48
|
+
if (this.position >= this.length) {
|
|
49
|
+
throw new Error('Out of data');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.partial |=
|
|
53
|
+
this.view.getUint8(this.position) << this.partialCount;
|
|
54
|
+
debugLog(
|
|
55
|
+
`getBits(${n}), add byte, position ${this.position}, bits ${this.partial.toString(2).padStart(this.partialCount, '0').substr(-this.partialCount)}`
|
|
56
|
+
);
|
|
57
|
+
this.position += 1;
|
|
58
|
+
this.partialCount += 8;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Return bits from the right
|
|
62
|
+
const result = this.partial & MASK[n];
|
|
63
|
+
this.partial >>= n;
|
|
64
|
+
this.partialCount -= n;
|
|
65
|
+
debugLog(
|
|
66
|
+
`getBits(${n}), end, position ${this.position}, bits ${this.partial.toString(2).padStart(this.partialCount, '0').substr(-this.partialCount)}, result ${result.toString(2).padStart(n, '0')} (${result})`
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The amount of bytes left in the buffer
|
|
73
|
+
remainingLength() {
|
|
74
|
+
return this.length - this.position;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
seek(position: number, partialCount = 0) {
|
|
78
|
+
this.position = position;
|
|
79
|
+
this.partial = 0;
|
|
80
|
+
this.partialCount = partialCount;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// FIXME - need to figure out how this is written. Does it update just one byte?
|
|
84
|
+
// FIXME - I don't see a flush method.
|
|
85
|
+
setBits(n: number, value: number) {
|
|
86
|
+
// Add the new bits to the right
|
|
87
|
+
this.partial <<= n;
|
|
88
|
+
this.partial |= value;
|
|
89
|
+
this.partialCount += n;
|
|
90
|
+
|
|
91
|
+
while (this.partialCount >= 8) {
|
|
92
|
+
this.view.setUint8(
|
|
93
|
+
this.position,
|
|
94
|
+
this.partial >> (this.partialCount - 8)
|
|
95
|
+
);
|
|
96
|
+
this.position += 1;
|
|
97
|
+
this.partialCount -= 8;
|
|
98
|
+
this.partial &= MASK[this.partialCount];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { BitData } from './bit-data';
|
|
3
|
+
import { Coords3d } from '../types/coords';
|
|
4
|
+
import { NbtCompound } from '../nbt/nbt-compound';
|
|
5
|
+
import { NbtList } from '../nbt/nbt-list';
|
|
6
|
+
import { NbtLongArray } from '../nbt/nbt-long-array';
|
|
7
|
+
import { NbtString } from '../nbt/nbt-string';
|
|
8
|
+
|
|
9
|
+
const BLOCKS_PER_CHUNK = 16 * 16 * 16;
|
|
10
|
+
const debugLog = debug('block-data');
|
|
11
|
+
|
|
12
|
+
function parseBlockData(
|
|
13
|
+
blockIds: number[],
|
|
14
|
+
bitData: BitData,
|
|
15
|
+
paletteMap: Map<number, string>
|
|
16
|
+
) {
|
|
17
|
+
let bitsRead = 0;
|
|
18
|
+
let blockIdsRead = 0;
|
|
19
|
+
const bitsNeededForPalette = Math.max(
|
|
20
|
+
4,
|
|
21
|
+
Math.ceil(Math.log2(paletteMap.size || 1))
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
while (bitData.remainingLength() > 0) {
|
|
25
|
+
while (bitsRead + bitsNeededForPalette <= 64) {
|
|
26
|
+
const blockId = bitData.getBits(bitsNeededForPalette);
|
|
27
|
+
|
|
28
|
+
if (!paletteMap.has(blockId)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Block ID ${blockId}, index ${blockIdsRead} not found in palette when reading index ${blockIdsRead}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
debugLog(
|
|
35
|
+
`Block ID ${blockId} (${paletteMap.get(blockId)}) at index ${blockIdsRead}`
|
|
36
|
+
);
|
|
37
|
+
blockIds.push(blockId);
|
|
38
|
+
bitsRead += bitsNeededForPalette;
|
|
39
|
+
blockIdsRead += 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (bitsRead < 64) {
|
|
43
|
+
// These extra bits can have any values. They are not
|
|
44
|
+
// initialized before writing to disk.
|
|
45
|
+
bitData.getBits(64 - bitsRead);
|
|
46
|
+
debugLog(`Skipped ${64 - bitsRead} bits`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
bitsRead = 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return blockIds;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class BlockData {
|
|
56
|
+
static fromPaletteBlockStates(
|
|
57
|
+
dataVersion: number,
|
|
58
|
+
palette: NbtList<NbtCompound>,
|
|
59
|
+
blockStates?: NbtLongArray
|
|
60
|
+
) {
|
|
61
|
+
// Convert the palette values to a map
|
|
62
|
+
const paletteSize = palette.data.length;
|
|
63
|
+
const paletteMap = new Map<number, string>();
|
|
64
|
+
|
|
65
|
+
if (dataVersion < 0) {
|
|
66
|
+
throw new Error('Data version is required to read block data');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < paletteSize; i++) {
|
|
70
|
+
const name = palette.data[i].findChild<NbtString>('Name');
|
|
71
|
+
|
|
72
|
+
if (name) {
|
|
73
|
+
paletteMap.set(i, name.data);
|
|
74
|
+
} else {
|
|
75
|
+
throw new Error(`Palette entry ${i} does not have a name`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const blockIds: number[] = [];
|
|
80
|
+
|
|
81
|
+
if (blockStates) {
|
|
82
|
+
const bitData = BitData.fromLongArrayTag(blockStates);
|
|
83
|
+
parseBlockData(blockIds, bitData, paletteMap);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Fill the rest of the blocks with the first element in the palette.
|
|
87
|
+
// The documentation says that a chunk could be filled with just the
|
|
88
|
+
// first index in the palette and not have any block data.
|
|
89
|
+
while (blockIds.length < BLOCKS_PER_CHUNK) {
|
|
90
|
+
blockIds.push(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Safety
|
|
94
|
+
while (blockIds.length > BLOCKS_PER_CHUNK) {
|
|
95
|
+
blockIds.pop();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return new BlockData(paletteMap, blockIds);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
constructor(
|
|
102
|
+
public paletteMap: Map<number, string>,
|
|
103
|
+
public blockIds: number[]
|
|
104
|
+
) {}
|
|
105
|
+
|
|
106
|
+
chunkCoordinatesToIndex([x, y, z]: Coords3d) {
|
|
107
|
+
return (y << 8) | (z << 4) | x;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
findBlocksByName(name: string): number[] {
|
|
111
|
+
const number = Array.from(this.paletteMap.keys()).find(
|
|
112
|
+
(key) => this.paletteMap.get(key) === name
|
|
113
|
+
);
|
|
114
|
+
const result = [];
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < this.blockIds.length; i++) {
|
|
117
|
+
if (this.blockIds[i] === number) {
|
|
118
|
+
result.push(i);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getBlockByIndex(index: number) {
|
|
126
|
+
const blockId = this.blockIds[index];
|
|
127
|
+
const blockName = this.paletteMap.get(blockId);
|
|
128
|
+
|
|
129
|
+
if (!blockName) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Block ID ${blockId} not found in palette when reading index ${index}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return blockName;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getBlockByChunkCoordinates(coordinates: Coords3d) {
|
|
139
|
+
const index = this.chunkCoordinatesToIndex(coordinates);
|
|
140
|
+
|
|
141
|
+
return this.getBlockByIndex(index);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
indexToChunkCoordinates(index: number): Coords3d {
|
|
145
|
+
const x = index & 0xf;
|
|
146
|
+
const y = (index >> 8) & 0xf;
|
|
147
|
+
const z = (index >> 4) & 0xf;
|
|
148
|
+
|
|
149
|
+
return [x, y, z];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
toArray() {
|
|
153
|
+
let errors: any[] = [];
|
|
154
|
+
|
|
155
|
+
const result = this.blockIds.map((blockId, index) => {
|
|
156
|
+
const blockName = this.paletteMap.get(blockId);
|
|
157
|
+
|
|
158
|
+
if (!blockName) {
|
|
159
|
+
errors.push({
|
|
160
|
+
index,
|
|
161
|
+
chunkCoordinates: this.indexToChunkCoordinates(index),
|
|
162
|
+
blockId,
|
|
163
|
+
paletteLength: this.paletteMap.size,
|
|
164
|
+
palette: Array.from(this.paletteMap.values()),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
chunkCoordinates: this.indexToChunkCoordinates(index),
|
|
170
|
+
blockName,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (errors.length) {
|
|
175
|
+
throw new Error(JSON.stringify(errors));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
}
|