minerender 2.1.0-alpha.90 → 2.1.0-alpha.92
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/dist/browser/index.d.mts +22 -5
- package/dist/browser/index.d.ts +22 -5
- package/dist/browser/index.js +180 -13
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +180 -13
- package/dist/browser/index.mjs.map +1 -1
- package/dist/bundle.js +183 -13
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +22 -5
- package/dist/node/index.d.ts +22 -5
- package/dist/node/index.js +180 -13
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +180 -13
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node/index.mjs
CHANGED
|
@@ -28972,17 +28972,138 @@ var ChunkData = class _ChunkData {
|
|
|
28972
28972
|
}
|
|
28973
28973
|
};
|
|
28974
28974
|
|
|
28975
|
+
// src/world/_compression/Lz4.ts
|
|
28976
|
+
init_MineRenderError();
|
|
28977
|
+
function decodeAnvilLz4(data) {
|
|
28978
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
28979
|
+
const blocks = [];
|
|
28980
|
+
let cursor = 0, totalSize = 0;
|
|
28981
|
+
while (true) {
|
|
28982
|
+
if (cursor + 21 > data.length) throw invalid("truncated block header or missing terminator");
|
|
28983
|
+
if (view.getUint32(cursor) !== 1280980034 || view.getUint32(cursor + 4) !== 1819239275) {
|
|
28984
|
+
throw invalid("invalid block signature");
|
|
28985
|
+
}
|
|
28986
|
+
const token = data[cursor + 8];
|
|
28987
|
+
const method = token & 240;
|
|
28988
|
+
const size = view.getInt32(cursor + 9, true);
|
|
28989
|
+
const decodedSize = view.getInt32(cursor + 13, true);
|
|
28990
|
+
const checksum = view.getUint32(cursor + 17, true);
|
|
28991
|
+
cursor += 21;
|
|
28992
|
+
if (method !== 16 && method !== 32) throw invalid("unsupported block method");
|
|
28993
|
+
if (size < 0 || decodedSize < 0 || decodedSize > 2 ** (10 + (token & 15)) || size === 0 !== (decodedSize === 0) || method === 16 && size !== decodedSize) {
|
|
28994
|
+
throw invalid("invalid block lengths");
|
|
28995
|
+
}
|
|
28996
|
+
if (size === 0) {
|
|
28997
|
+
if (checksum !== 0 || cursor !== data.length) throw invalid("invalid terminator or trailing bytes");
|
|
28998
|
+
break;
|
|
28999
|
+
}
|
|
29000
|
+
if (cursor + size > data.length || decodedSize > size * 255) throw invalid("invalid block lengths");
|
|
29001
|
+
blocks.push({ start: cursor, size, decodedSize, raw: method === 16, checksum });
|
|
29002
|
+
totalSize += decodedSize;
|
|
29003
|
+
cursor += size;
|
|
29004
|
+
}
|
|
29005
|
+
const output = new Uint8Array(totalSize);
|
|
29006
|
+
let offset = 0;
|
|
29007
|
+
for (const block of blocks) {
|
|
29008
|
+
const source = data.subarray(block.start, block.start + block.size);
|
|
29009
|
+
const target = output.subarray(offset, offset + block.decodedSize);
|
|
29010
|
+
if (block.raw) target.set(source);
|
|
29011
|
+
else decodeBlock(source, target);
|
|
29012
|
+
if (checksum32(target) !== block.checksum) throw invalid("block checksum mismatch");
|
|
29013
|
+
offset += block.decodedSize;
|
|
29014
|
+
}
|
|
29015
|
+
return output;
|
|
29016
|
+
}
|
|
29017
|
+
function decodeBlock(source, target) {
|
|
29018
|
+
let input = 0, output = 0, matched = false;
|
|
29019
|
+
const length = (nibble) => {
|
|
29020
|
+
let value = nibble;
|
|
29021
|
+
if (value === 15) {
|
|
29022
|
+
let extension;
|
|
29023
|
+
do {
|
|
29024
|
+
if (input === source.length) throw invalid("truncated sequence length");
|
|
29025
|
+
extension = source[input++];
|
|
29026
|
+
value += extension;
|
|
29027
|
+
} while (extension === 255);
|
|
29028
|
+
}
|
|
29029
|
+
return value;
|
|
29030
|
+
};
|
|
29031
|
+
while (input < source.length) {
|
|
29032
|
+
const token = source[input++];
|
|
29033
|
+
const literals = length(token >>> 4);
|
|
29034
|
+
if (input + literals > source.length || output + literals > target.length) {
|
|
29035
|
+
throw invalid("literal sequence exceeds block bounds");
|
|
29036
|
+
}
|
|
29037
|
+
target.set(source.subarray(input, input + literals), output);
|
|
29038
|
+
input += literals;
|
|
29039
|
+
output += literals;
|
|
29040
|
+
if (input === source.length) {
|
|
29041
|
+
if (output !== target.length || matched && literals < 5) throw invalid("invalid final literal sequence");
|
|
29042
|
+
return;
|
|
29043
|
+
}
|
|
29044
|
+
if (input + 2 > source.length) throw invalid("truncated match offset");
|
|
29045
|
+
const distance = source[input] | source[input + 1] << 8;
|
|
29046
|
+
input += 2;
|
|
29047
|
+
if (distance === 0 || distance > output) throw invalid("match offset exceeds block history");
|
|
29048
|
+
const count = length(token & 15) + 4;
|
|
29049
|
+
if (output > target.length - 12 || output + count > target.length - 5) {
|
|
29050
|
+
throw invalid("match exceeds block bounds");
|
|
29051
|
+
}
|
|
29052
|
+
for (let end = output + count; output < end; output++) target[output] = target[output - distance];
|
|
29053
|
+
matched = true;
|
|
29054
|
+
}
|
|
29055
|
+
throw invalid("missing final literal sequence");
|
|
29056
|
+
}
|
|
29057
|
+
function checksum32(data) {
|
|
29058
|
+
const prime1 = 2654435761, prime2 = 2246822519, prime3 = 3266489917, prime4 = 668265263, prime5 = 374761393;
|
|
29059
|
+
const seed = 2538058380;
|
|
29060
|
+
const rotate = (value, bits) => value << bits | value >>> 32 - bits;
|
|
29061
|
+
const round = (value, word) => Math.imul(rotate(value + Math.imul(word, prime2), 13), prime1);
|
|
29062
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
29063
|
+
let cursor = 0, hash;
|
|
29064
|
+
if (data.length >= 16) {
|
|
29065
|
+
let a = seed + prime1 + prime2, b = seed + prime2, c = seed, d = seed - prime1;
|
|
29066
|
+
while (cursor + 16 <= data.length) {
|
|
29067
|
+
a = round(a, view.getUint32(cursor, true));
|
|
29068
|
+
b = round(b, view.getUint32(cursor + 4, true));
|
|
29069
|
+
c = round(c, view.getUint32(cursor + 8, true));
|
|
29070
|
+
d = round(d, view.getUint32(cursor + 12, true));
|
|
29071
|
+
cursor += 16;
|
|
29072
|
+
}
|
|
29073
|
+
hash = rotate(a, 1) + rotate(b, 7) + rotate(c, 12) + rotate(d, 18);
|
|
29074
|
+
} else {
|
|
29075
|
+
hash = seed + prime5;
|
|
29076
|
+
}
|
|
29077
|
+
hash += data.length;
|
|
29078
|
+
while (cursor + 4 <= data.length) {
|
|
29079
|
+
hash = Math.imul(rotate(hash + Math.imul(view.getUint32(cursor, true), prime3), 17), prime4);
|
|
29080
|
+
cursor += 4;
|
|
29081
|
+
}
|
|
29082
|
+
while (cursor < data.length) {
|
|
29083
|
+
hash = Math.imul(rotate(hash + Math.imul(data[cursor++], prime5), 11), prime1);
|
|
29084
|
+
}
|
|
29085
|
+
hash = Math.imul(hash ^ hash >>> 15, prime2);
|
|
29086
|
+
hash = Math.imul(hash ^ hash >>> 13, prime3);
|
|
29087
|
+
return (hash ^ hash >>> 16) & 268435455;
|
|
29088
|
+
}
|
|
29089
|
+
function invalid(detail) {
|
|
29090
|
+
return new MineRenderError(`Invalid Anvil LZ4 stream: ${detail}`);
|
|
29091
|
+
}
|
|
29092
|
+
|
|
28975
29093
|
// src/world/AnvilParser.ts
|
|
28976
29094
|
var AnvilParser = class {
|
|
28977
29095
|
/** Lists occupied chunk positions within the region, using local coordinates from 0 to 31. */
|
|
28978
29096
|
static getChunkList(data) {
|
|
28979
29097
|
return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
|
|
28980
29098
|
}
|
|
28981
|
-
/** Decodes all stored chunks. Gzip, zlib, and uncompressed payloads are supported. */
|
|
28982
|
-
static async parse(data) {
|
|
29099
|
+
/** Decodes all stored chunks. Gzip, zlib, LZ4, and uncompressed payloads are supported. */
|
|
29100
|
+
static async parse(data, options = {}) {
|
|
29101
|
+
options.signal?.throwIfAborted();
|
|
28983
29102
|
const bytes = this.bytes(data);
|
|
28984
29103
|
const chunks = [];
|
|
28985
|
-
for (const location of this.locations(bytes))
|
|
29104
|
+
for (const location of this.locations(bytes)) {
|
|
29105
|
+
chunks.push(await this.abortable(this.readChunk(bytes, location, options), options.signal));
|
|
29106
|
+
}
|
|
28986
29107
|
return { chunks };
|
|
28987
29108
|
}
|
|
28988
29109
|
/**
|
|
@@ -28991,13 +29112,28 @@ var AnvilParser = class {
|
|
|
28991
29112
|
* @param localZ - Chunk z within the region, from 0 to 31.
|
|
28992
29113
|
* @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
|
|
28993
29114
|
*/
|
|
28994
|
-
static async parseChunk(data, localX, localZ) {
|
|
29115
|
+
static async parseChunk(data, localX, localZ, options = {}) {
|
|
29116
|
+
options.signal?.throwIfAborted();
|
|
28995
29117
|
if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
|
|
28996
29118
|
throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
|
|
28997
29119
|
}
|
|
28998
29120
|
const bytes = this.bytes(data);
|
|
28999
29121
|
const location = this.locations(bytes).find(({ x, z }) => x === localX && z === localZ);
|
|
29000
|
-
return location ? this.readChunk(bytes, location) : void 0;
|
|
29122
|
+
return location ? this.abortable(this.readChunk(bytes, location, options), options.signal) : void 0;
|
|
29123
|
+
}
|
|
29124
|
+
static async abortable(promise, signal) {
|
|
29125
|
+
if (!signal) return promise;
|
|
29126
|
+
let abort;
|
|
29127
|
+
const cancelled = new Promise((_, reject) => {
|
|
29128
|
+
abort = () => reject(signal.reason);
|
|
29129
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
29130
|
+
if (signal.aborted) abort();
|
|
29131
|
+
});
|
|
29132
|
+
try {
|
|
29133
|
+
return await Promise.race([promise, cancelled]);
|
|
29134
|
+
} finally {
|
|
29135
|
+
signal.removeEventListener("abort", abort);
|
|
29136
|
+
}
|
|
29001
29137
|
}
|
|
29002
29138
|
static bytes(data) {
|
|
29003
29139
|
return data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
@@ -29023,18 +29159,41 @@ var AnvilParser = class {
|
|
|
29023
29159
|
}
|
|
29024
29160
|
return locations;
|
|
29025
29161
|
}
|
|
29026
|
-
static async readChunk(data, location) {
|
|
29027
|
-
|
|
29028
|
-
|
|
29029
|
-
|
|
29030
|
-
|
|
29162
|
+
static async readChunk(data, location, options) {
|
|
29163
|
+
options.signal?.throwIfAborted();
|
|
29164
|
+
const format2 = data[location.offset + 4];
|
|
29165
|
+
const external = (format2 & 128) !== 0;
|
|
29166
|
+
const compression = format2 & 127;
|
|
29167
|
+
if (compression !== 1 && compression !== 2 && compression !== 3 && compression !== 4) {
|
|
29168
|
+
throw new MineRenderError(`Unsupported Anvil compression ${compression}`);
|
|
29031
29169
|
}
|
|
29032
29170
|
let payload = data.subarray(location.offset + 5, location.offset + 4 + location.length);
|
|
29033
|
-
|
|
29171
|
+
let expected;
|
|
29172
|
+
if (external) {
|
|
29173
|
+
if (!options.region || !options.readExternalChunk) {
|
|
29174
|
+
throw new MineRenderError("External Anvil .mcc chunks require region coordinates and readExternalChunk");
|
|
29175
|
+
}
|
|
29176
|
+
if (![options.region.x, options.region.z].every(Number.isSafeInteger)) {
|
|
29177
|
+
throw new RangeError("Anvil region coordinates must be safe integers");
|
|
29178
|
+
}
|
|
29179
|
+
expected = { x: options.region.x * 32 + location.x, z: options.region.z * 32 + location.z };
|
|
29180
|
+
if (![expected.x, expected.z].every(Number.isSafeInteger)) {
|
|
29181
|
+
throw new RangeError("External Anvil chunk coordinates must be safe integers");
|
|
29182
|
+
}
|
|
29183
|
+
const externalData = await options.readExternalChunk(expected.x, expected.z, options.signal);
|
|
29184
|
+
options.signal?.throwIfAborted();
|
|
29185
|
+
if (externalData === void 0) throw new MineRenderError(`External Anvil chunk c.${expected.x}.${expected.z}.mcc is missing`);
|
|
29186
|
+
payload = this.bytes(externalData);
|
|
29187
|
+
}
|
|
29188
|
+
if (compression === 4) {
|
|
29189
|
+
payload = decodeAnvilLz4(payload);
|
|
29190
|
+
} else if (compression !== 3) {
|
|
29034
29191
|
const stream = new Blob([payload]).stream().pipeThrough(new DecompressionStream(compression === 1 ? "gzip" : "deflate"));
|
|
29035
29192
|
payload = new Uint8Array(await new Response(stream).arrayBuffer());
|
|
29036
29193
|
}
|
|
29194
|
+
options.signal?.throwIfAborted();
|
|
29037
29195
|
const nbt = await NBTHelper.fromBuffer(payload, "big");
|
|
29196
|
+
options.signal?.throwIfAborted();
|
|
29038
29197
|
const root = nbt.value;
|
|
29039
29198
|
const level = root.Level?.type === "compound" ? root.Level.value : root;
|
|
29040
29199
|
const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
|
|
@@ -29047,6 +29206,9 @@ var AnvilParser = class {
|
|
|
29047
29206
|
if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
|
|
29048
29207
|
throw new MineRenderError("Anvil chunk coordinates do not match its region location");
|
|
29049
29208
|
}
|
|
29209
|
+
if (expected && (chunk.x !== expected.x || chunk.z !== expected.z)) {
|
|
29210
|
+
throw new MineRenderError(`Anvil chunk coordinates do not match external file c.${expected.x}.${expected.z}.mcc`);
|
|
29211
|
+
}
|
|
29050
29212
|
for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
|
|
29051
29213
|
const y = this.integer(section.Y, "section Y");
|
|
29052
29214
|
if (section.Blocks || section.Data || section.Add) {
|
|
@@ -29178,6 +29340,7 @@ var AnvilWorldSource = class {
|
|
|
29178
29340
|
this.cachedBytes = 0;
|
|
29179
29341
|
this.maxCachedRegions = options.maxCachedRegions ?? 4;
|
|
29180
29342
|
this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
|
|
29343
|
+
this.readExternalChunk = options.readExternalChunk;
|
|
29181
29344
|
for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
|
|
29182
29345
|
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
|
|
29183
29346
|
}
|
|
@@ -29191,7 +29354,11 @@ var AnvilWorldSource = class {
|
|
|
29191
29354
|
const data = await this.getRegion(regionX, regionZ, signal);
|
|
29192
29355
|
signal?.throwIfAborted();
|
|
29193
29356
|
if (!data) return void 0;
|
|
29194
|
-
const chunk = await
|
|
29357
|
+
const chunk = await AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32, {
|
|
29358
|
+
region: { x: regionX, z: regionZ },
|
|
29359
|
+
readExternalChunk: this.readExternalChunk,
|
|
29360
|
+
signal
|
|
29361
|
+
});
|
|
29195
29362
|
signal?.throwIfAborted();
|
|
29196
29363
|
if (chunk && (chunk.x !== x || chunk.z !== z)) {
|
|
29197
29364
|
throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
|
|
@@ -29222,7 +29389,7 @@ var AnvilWorldSource = class {
|
|
|
29222
29389
|
settled: false,
|
|
29223
29390
|
promise: Promise.resolve().then(() => {
|
|
29224
29391
|
controller.signal.throwIfAborted();
|
|
29225
|
-
return this.readRegion(x, z, controller.signal);
|
|
29392
|
+
return abortable2(this.readRegion(x, z, controller.signal), controller.signal);
|
|
29226
29393
|
}).then((data) => {
|
|
29227
29394
|
controller.signal.throwIfAborted();
|
|
29228
29395
|
let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);
|