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.
Files changed (231) hide show
  1. package/.editorconfig +14 -0
  2. package/.prettierrc +4 -0
  3. package/README.md +373 -0
  4. package/dist/bin/mca-chunks.d.ts +3 -0
  5. package/dist/bin/mca-chunks.d.ts.map +1 -0
  6. package/dist/bin/mca-chunks.js +43 -0
  7. package/dist/bin/mca-chunks.js.map +1 -0
  8. package/dist/bin/mca-find-chunks-with-signs.d.ts +3 -0
  9. package/dist/bin/mca-find-chunks-with-signs.d.ts.map +1 -0
  10. package/dist/bin/mca-find-chunks-with-signs.js +79 -0
  11. package/dist/bin/mca-find-chunks-with-signs.js.map +1 -0
  12. package/dist/bin/mca-json.d.ts +3 -0
  13. package/dist/bin/mca-json.d.ts.map +1 -0
  14. package/dist/bin/mca-json.js +73 -0
  15. package/dist/bin/mca-json.js.map +1 -0
  16. package/dist/bin/mca-trim-chunks-without-signs.d.ts +3 -0
  17. package/dist/bin/mca-trim-chunks-without-signs.d.ts.map +1 -0
  18. package/dist/bin/mca-trim-chunks-without-signs.js +117 -0
  19. package/dist/bin/mca-trim-chunks-without-signs.js.map +1 -0
  20. package/dist/bin/nbt-get-player-location.d.ts +3 -0
  21. package/dist/bin/nbt-get-player-location.d.ts.map +1 -0
  22. package/dist/bin/nbt-get-player-location.js +83 -0
  23. package/dist/bin/nbt-get-player-location.js.map +1 -0
  24. package/dist/bin/nbt-json.d.ts +3 -0
  25. package/dist/bin/nbt-json.d.ts.map +1 -0
  26. package/dist/bin/nbt-json.js +64 -0
  27. package/dist/bin/nbt-json.js.map +1 -0
  28. package/dist/block/banner.d.ts +13 -0
  29. package/dist/block/banner.d.ts.map +1 -0
  30. package/dist/block/banner.js +28 -0
  31. package/dist/block/banner.js.map +1 -0
  32. package/dist/block/barrel.d.ts +17 -0
  33. package/dist/block/barrel.d.ts.map +1 -0
  34. package/dist/block/barrel.js +21 -0
  35. package/dist/block/barrel.js.map +1 -0
  36. package/dist/block/beacon.d.ts +14 -0
  37. package/dist/block/beacon.d.ts.map +1 -0
  38. package/dist/block/beacon.js +14 -0
  39. package/dist/block/beacon.js.map +1 -0
  40. package/dist/block/bed.d.ts +4 -0
  41. package/dist/block/bed.d.ts.map +1 -0
  42. package/dist/block/bed.js +4 -0
  43. package/dist/block/bed.js.map +1 -0
  44. package/dist/block/beehive.d.ts +9 -0
  45. package/dist/block/beehive.d.ts.map +1 -0
  46. package/dist/block/beehive.js +10 -0
  47. package/dist/block/beehive.js.map +1 -0
  48. package/dist/block/bell.d.ts +4 -0
  49. package/dist/block/bell.d.ts.map +1 -0
  50. package/dist/block/bell.js +4 -0
  51. package/dist/block/bell.js.map +1 -0
  52. package/dist/block/block.d.ts +23 -0
  53. package/dist/block/block.d.ts.map +1 -0
  54. package/dist/block/block.js +49 -0
  55. package/dist/block/block.js.map +1 -0
  56. package/dist/block/generic.d.ts +10 -0
  57. package/dist/block/generic.d.ts.map +1 -0
  58. package/dist/block/generic.js +14 -0
  59. package/dist/block/generic.js.map +1 -0
  60. package/dist/block/has-entity-data.d.ts +8 -0
  61. package/dist/block/has-entity-data.d.ts.map +1 -0
  62. package/dist/block/has-entity-data.js +12 -0
  63. package/dist/block/has-entity-data.js.map +1 -0
  64. package/dist/block/mixins/custom-name.d.ts +5 -0
  65. package/dist/block/mixins/custom-name.d.ts.map +1 -0
  66. package/dist/block/mixins/custom-name.js +7 -0
  67. package/dist/block/mixins/custom-name.js.map +1 -0
  68. package/dist/block/mixins/lock.d.ts +5 -0
  69. package/dist/block/mixins/lock.d.ts.map +1 -0
  70. package/dist/block/mixins/lock.js +7 -0
  71. package/dist/block/mixins/lock.js.map +1 -0
  72. package/dist/block/mixins/mixin.d.ts +27 -0
  73. package/dist/block/mixins/mixin.d.ts.map +1 -0
  74. package/dist/block/mixins/mixin.js +36 -0
  75. package/dist/block/mixins/mixin.js.map +1 -0
  76. package/dist/block/sign.d.ts +15 -0
  77. package/dist/block/sign.d.ts.map +1 -0
  78. package/dist/block/sign.js +91 -0
  79. package/dist/block/sign.js.map +1 -0
  80. package/dist/index.d.ts +39 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +39 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/lib/anvil.d.ts +54 -0
  85. package/dist/lib/anvil.d.ts.map +1 -0
  86. package/dist/lib/anvil.js +141 -0
  87. package/dist/lib/anvil.js.map +1 -0
  88. package/dist/lib/binary-data.d.ts +48 -0
  89. package/dist/lib/binary-data.d.ts.map +1 -0
  90. package/dist/lib/binary-data.js +188 -0
  91. package/dist/lib/binary-data.js.map +1 -0
  92. package/dist/lib/bit-data.d.ts +16 -0
  93. package/dist/lib/bit-data.d.ts.map +1 -0
  94. package/dist/lib/bit-data.js +75 -0
  95. package/dist/lib/bit-data.js.map +1 -0
  96. package/dist/lib/block-data.d.ts +20 -0
  97. package/dist/lib/block-data.d.ts.map +1 -0
  98. package/dist/lib/block-data.js +125 -0
  99. package/dist/lib/block-data.js.map +1 -0
  100. package/dist/lib/chunk.d.ts +112 -0
  101. package/dist/lib/chunk.d.ts.map +1 -0
  102. package/dist/lib/chunk.js +299 -0
  103. package/dist/lib/chunk.js.map +1 -0
  104. package/dist/nbt/nbt-base.d.ts +20 -0
  105. package/dist/nbt/nbt-base.d.ts.map +1 -0
  106. package/dist/nbt/nbt-base.js +30 -0
  107. package/dist/nbt/nbt-base.js.map +1 -0
  108. package/dist/nbt/nbt-byte-array.d.ts +13 -0
  109. package/dist/nbt/nbt-byte-array.d.ts.map +1 -0
  110. package/dist/nbt/nbt-byte-array.js +30 -0
  111. package/dist/nbt/nbt-byte-array.js.map +1 -0
  112. package/dist/nbt/nbt-byte.d.ts +13 -0
  113. package/dist/nbt/nbt-byte.d.ts.map +1 -0
  114. package/dist/nbt/nbt-byte.js +25 -0
  115. package/dist/nbt/nbt-byte.js.map +1 -0
  116. package/dist/nbt/nbt-compound.d.ts +15 -0
  117. package/dist/nbt/nbt-compound.d.ts.map +1 -0
  118. package/dist/nbt/nbt-compound.js +68 -0
  119. package/dist/nbt/nbt-compound.js.map +1 -0
  120. package/dist/nbt/nbt-double.d.ts +13 -0
  121. package/dist/nbt/nbt-double.d.ts.map +1 -0
  122. package/dist/nbt/nbt-double.js +25 -0
  123. package/dist/nbt/nbt-double.js.map +1 -0
  124. package/dist/nbt/nbt-end.d.ts +11 -0
  125. package/dist/nbt/nbt-end.d.ts.map +1 -0
  126. package/dist/nbt/nbt-end.js +22 -0
  127. package/dist/nbt/nbt-end.js.map +1 -0
  128. package/dist/nbt/nbt-float.d.ts +13 -0
  129. package/dist/nbt/nbt-float.d.ts.map +1 -0
  130. package/dist/nbt/nbt-float.js +25 -0
  131. package/dist/nbt/nbt-float.js.map +1 -0
  132. package/dist/nbt/nbt-int-array.d.ts +13 -0
  133. package/dist/nbt/nbt-int-array.d.ts.map +1 -0
  134. package/dist/nbt/nbt-int-array.js +30 -0
  135. package/dist/nbt/nbt-int-array.js.map +1 -0
  136. package/dist/nbt/nbt-int.d.ts +13 -0
  137. package/dist/nbt/nbt-int.d.ts.map +1 -0
  138. package/dist/nbt/nbt-int.js +25 -0
  139. package/dist/nbt/nbt-int.js.map +1 -0
  140. package/dist/nbt/nbt-list.d.ts +17 -0
  141. package/dist/nbt/nbt-list.d.ts.map +1 -0
  142. package/dist/nbt/nbt-list.js +70 -0
  143. package/dist/nbt/nbt-list.js.map +1 -0
  144. package/dist/nbt/nbt-long-array.d.ts +13 -0
  145. package/dist/nbt/nbt-long-array.d.ts.map +1 -0
  146. package/dist/nbt/nbt-long-array.js +32 -0
  147. package/dist/nbt/nbt-long-array.js.map +1 -0
  148. package/dist/nbt/nbt-long.d.ts +13 -0
  149. package/dist/nbt/nbt-long.d.ts.map +1 -0
  150. package/dist/nbt/nbt-long.js +25 -0
  151. package/dist/nbt/nbt-long.js.map +1 -0
  152. package/dist/nbt/nbt-short.d.ts +13 -0
  153. package/dist/nbt/nbt-short.d.ts.map +1 -0
  154. package/dist/nbt/nbt-short.js +25 -0
  155. package/dist/nbt/nbt-short.js.map +1 -0
  156. package/dist/nbt/nbt-string.d.ts +13 -0
  157. package/dist/nbt/nbt-string.d.ts.map +1 -0
  158. package/dist/nbt/nbt-string.js +27 -0
  159. package/dist/nbt/nbt-string.js.map +1 -0
  160. package/dist/nbt/nbt-tag-type.d.ts +16 -0
  161. package/dist/nbt/nbt-tag-type.d.ts.map +1 -0
  162. package/dist/nbt/nbt-tag-type.js +17 -0
  163. package/dist/nbt/nbt-tag-type.js.map +1 -0
  164. package/dist/nbt/nbt.d.ts +11 -0
  165. package/dist/nbt/nbt.d.ts.map +1 -0
  166. package/dist/nbt/nbt.js +57 -0
  167. package/dist/nbt/nbt.js.map +1 -0
  168. package/dist/nbt/snbt-data.d.ts +18 -0
  169. package/dist/nbt/snbt-data.d.ts.map +1 -0
  170. package/dist/nbt/snbt-data.js +34 -0
  171. package/dist/nbt/snbt-data.js.map +1 -0
  172. package/dist/nbt/snbt-parse.d.ts +3 -0
  173. package/dist/nbt/snbt-parse.d.ts.map +1 -0
  174. package/dist/nbt/snbt-parse.js +201 -0
  175. package/dist/nbt/snbt-parse.js.map +1 -0
  176. package/dist/nbt/snbt-to-nbt.d.ts +7 -0
  177. package/dist/nbt/snbt-to-nbt.d.ts.map +1 -0
  178. package/dist/nbt/snbt-to-nbt.js +178 -0
  179. package/dist/nbt/snbt-to-nbt.js.map +1 -0
  180. package/dist/types/coords.d.ts +3 -0
  181. package/dist/types/coords.d.ts.map +1 -0
  182. package/dist/types/coords.js +2 -0
  183. package/dist/types/coords.js.map +1 -0
  184. package/package.json +28 -0
  185. package/src/bin/mca-chunks.ts +54 -0
  186. package/src/bin/mca-find-chunks-with-signs.ts +109 -0
  187. package/src/bin/mca-json.ts +96 -0
  188. package/src/bin/mca-trim-chunks-without-signs.ts +146 -0
  189. package/src/bin/nbt-get-player-location.ts +102 -0
  190. package/src/bin/nbt-json.ts +85 -0
  191. package/src/block/banner.ts +50 -0
  192. package/src/block/barrel.ts +34 -0
  193. package/src/block/beacon.ts +20 -0
  194. package/src/block/bed.ts +3 -0
  195. package/src/block/beehive.ts +17 -0
  196. package/src/block/bell.ts +3 -0
  197. package/src/block/block.ts +62 -0
  198. package/src/block/generic.ts +14 -0
  199. package/src/block/has-entity-data.ts +20 -0
  200. package/src/block/mixins/custom-name.ts +8 -0
  201. package/src/block/mixins/lock.ts +8 -0
  202. package/src/block/mixins/mixin.ts +53 -0
  203. package/src/block/sign.ts +121 -0
  204. package/src/index.ts +38 -0
  205. package/src/lib/anvil.ts +178 -0
  206. package/src/lib/binary-data.ts +247 -0
  207. package/src/lib/bit-data.ts +101 -0
  208. package/src/lib/block-data.ts +180 -0
  209. package/src/lib/chunk.ts +389 -0
  210. package/src/nbt/nbt-base.ts +38 -0
  211. package/src/nbt/nbt-byte-array.ts +38 -0
  212. package/src/nbt/nbt-byte.ts +31 -0
  213. package/src/nbt/nbt-compound.ts +95 -0
  214. package/src/nbt/nbt-double.ts +31 -0
  215. package/src/nbt/nbt-end.ts +27 -0
  216. package/src/nbt/nbt-float.ts +31 -0
  217. package/src/nbt/nbt-int-array.ts +38 -0
  218. package/src/nbt/nbt-int.ts +31 -0
  219. package/src/nbt/nbt-list.ts +103 -0
  220. package/src/nbt/nbt-long-array.ts +40 -0
  221. package/src/nbt/nbt-long.ts +31 -0
  222. package/src/nbt/nbt-short.ts +31 -0
  223. package/src/nbt/nbt-string.ts +35 -0
  224. package/src/nbt/nbt-tag-type.ts +15 -0
  225. package/src/nbt/nbt.ts +70 -0
  226. package/src/nbt/snbt-data.ts +70 -0
  227. package/src/nbt/snbt-parse.ts +256 -0
  228. package/src/nbt/snbt-to-nbt.ts +219 -0
  229. package/src/types/coords.ts +2 -0
  230. package/src/types/neodoc.d.ts +28 -0
  231. package/tsconfig.json +25 -0
@@ -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
+ }