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/browser/index.mjs
CHANGED
|
@@ -28923,17 +28923,138 @@ var ChunkData = class _ChunkData {
|
|
|
28923
28923
|
}
|
|
28924
28924
|
};
|
|
28925
28925
|
|
|
28926
|
+
// src/world/_compression/Lz4.ts
|
|
28927
|
+
init_MineRenderError();
|
|
28928
|
+
function decodeAnvilLz4(data) {
|
|
28929
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
28930
|
+
const blocks = [];
|
|
28931
|
+
let cursor = 0, totalSize = 0;
|
|
28932
|
+
while (true) {
|
|
28933
|
+
if (cursor + 21 > data.length) throw invalid("truncated block header or missing terminator");
|
|
28934
|
+
if (view.getUint32(cursor) !== 1280980034 || view.getUint32(cursor + 4) !== 1819239275) {
|
|
28935
|
+
throw invalid("invalid block signature");
|
|
28936
|
+
}
|
|
28937
|
+
const token = data[cursor + 8];
|
|
28938
|
+
const method = token & 240;
|
|
28939
|
+
const size = view.getInt32(cursor + 9, true);
|
|
28940
|
+
const decodedSize = view.getInt32(cursor + 13, true);
|
|
28941
|
+
const checksum = view.getUint32(cursor + 17, true);
|
|
28942
|
+
cursor += 21;
|
|
28943
|
+
if (method !== 16 && method !== 32) throw invalid("unsupported block method");
|
|
28944
|
+
if (size < 0 || decodedSize < 0 || decodedSize > 2 ** (10 + (token & 15)) || size === 0 !== (decodedSize === 0) || method === 16 && size !== decodedSize) {
|
|
28945
|
+
throw invalid("invalid block lengths");
|
|
28946
|
+
}
|
|
28947
|
+
if (size === 0) {
|
|
28948
|
+
if (checksum !== 0 || cursor !== data.length) throw invalid("invalid terminator or trailing bytes");
|
|
28949
|
+
break;
|
|
28950
|
+
}
|
|
28951
|
+
if (cursor + size > data.length || decodedSize > size * 255) throw invalid("invalid block lengths");
|
|
28952
|
+
blocks.push({ start: cursor, size, decodedSize, raw: method === 16, checksum });
|
|
28953
|
+
totalSize += decodedSize;
|
|
28954
|
+
cursor += size;
|
|
28955
|
+
}
|
|
28956
|
+
const output = new Uint8Array(totalSize);
|
|
28957
|
+
let offset = 0;
|
|
28958
|
+
for (const block of blocks) {
|
|
28959
|
+
const source = data.subarray(block.start, block.start + block.size);
|
|
28960
|
+
const target = output.subarray(offset, offset + block.decodedSize);
|
|
28961
|
+
if (block.raw) target.set(source);
|
|
28962
|
+
else decodeBlock(source, target);
|
|
28963
|
+
if (checksum32(target) !== block.checksum) throw invalid("block checksum mismatch");
|
|
28964
|
+
offset += block.decodedSize;
|
|
28965
|
+
}
|
|
28966
|
+
return output;
|
|
28967
|
+
}
|
|
28968
|
+
function decodeBlock(source, target) {
|
|
28969
|
+
let input = 0, output = 0, matched = false;
|
|
28970
|
+
const length = (nibble) => {
|
|
28971
|
+
let value = nibble;
|
|
28972
|
+
if (value === 15) {
|
|
28973
|
+
let extension;
|
|
28974
|
+
do {
|
|
28975
|
+
if (input === source.length) throw invalid("truncated sequence length");
|
|
28976
|
+
extension = source[input++];
|
|
28977
|
+
value += extension;
|
|
28978
|
+
} while (extension === 255);
|
|
28979
|
+
}
|
|
28980
|
+
return value;
|
|
28981
|
+
};
|
|
28982
|
+
while (input < source.length) {
|
|
28983
|
+
const token = source[input++];
|
|
28984
|
+
const literals = length(token >>> 4);
|
|
28985
|
+
if (input + literals > source.length || output + literals > target.length) {
|
|
28986
|
+
throw invalid("literal sequence exceeds block bounds");
|
|
28987
|
+
}
|
|
28988
|
+
target.set(source.subarray(input, input + literals), output);
|
|
28989
|
+
input += literals;
|
|
28990
|
+
output += literals;
|
|
28991
|
+
if (input === source.length) {
|
|
28992
|
+
if (output !== target.length || matched && literals < 5) throw invalid("invalid final literal sequence");
|
|
28993
|
+
return;
|
|
28994
|
+
}
|
|
28995
|
+
if (input + 2 > source.length) throw invalid("truncated match offset");
|
|
28996
|
+
const distance = source[input] | source[input + 1] << 8;
|
|
28997
|
+
input += 2;
|
|
28998
|
+
if (distance === 0 || distance > output) throw invalid("match offset exceeds block history");
|
|
28999
|
+
const count = length(token & 15) + 4;
|
|
29000
|
+
if (output > target.length - 12 || output + count > target.length - 5) {
|
|
29001
|
+
throw invalid("match exceeds block bounds");
|
|
29002
|
+
}
|
|
29003
|
+
for (let end = output + count; output < end; output++) target[output] = target[output - distance];
|
|
29004
|
+
matched = true;
|
|
29005
|
+
}
|
|
29006
|
+
throw invalid("missing final literal sequence");
|
|
29007
|
+
}
|
|
29008
|
+
function checksum32(data) {
|
|
29009
|
+
const prime1 = 2654435761, prime2 = 2246822519, prime3 = 3266489917, prime4 = 668265263, prime5 = 374761393;
|
|
29010
|
+
const seed = 2538058380;
|
|
29011
|
+
const rotate = (value, bits) => value << bits | value >>> 32 - bits;
|
|
29012
|
+
const round = (value, word) => Math.imul(rotate(value + Math.imul(word, prime2), 13), prime1);
|
|
29013
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
29014
|
+
let cursor = 0, hash;
|
|
29015
|
+
if (data.length >= 16) {
|
|
29016
|
+
let a = seed + prime1 + prime2, b = seed + prime2, c = seed, d = seed - prime1;
|
|
29017
|
+
while (cursor + 16 <= data.length) {
|
|
29018
|
+
a = round(a, view.getUint32(cursor, true));
|
|
29019
|
+
b = round(b, view.getUint32(cursor + 4, true));
|
|
29020
|
+
c = round(c, view.getUint32(cursor + 8, true));
|
|
29021
|
+
d = round(d, view.getUint32(cursor + 12, true));
|
|
29022
|
+
cursor += 16;
|
|
29023
|
+
}
|
|
29024
|
+
hash = rotate(a, 1) + rotate(b, 7) + rotate(c, 12) + rotate(d, 18);
|
|
29025
|
+
} else {
|
|
29026
|
+
hash = seed + prime5;
|
|
29027
|
+
}
|
|
29028
|
+
hash += data.length;
|
|
29029
|
+
while (cursor + 4 <= data.length) {
|
|
29030
|
+
hash = Math.imul(rotate(hash + Math.imul(view.getUint32(cursor, true), prime3), 17), prime4);
|
|
29031
|
+
cursor += 4;
|
|
29032
|
+
}
|
|
29033
|
+
while (cursor < data.length) {
|
|
29034
|
+
hash = Math.imul(rotate(hash + Math.imul(data[cursor++], prime5), 11), prime1);
|
|
29035
|
+
}
|
|
29036
|
+
hash = Math.imul(hash ^ hash >>> 15, prime2);
|
|
29037
|
+
hash = Math.imul(hash ^ hash >>> 13, prime3);
|
|
29038
|
+
return (hash ^ hash >>> 16) & 268435455;
|
|
29039
|
+
}
|
|
29040
|
+
function invalid(detail) {
|
|
29041
|
+
return new MineRenderError(`Invalid Anvil LZ4 stream: ${detail}`);
|
|
29042
|
+
}
|
|
29043
|
+
|
|
28926
29044
|
// src/world/AnvilParser.ts
|
|
28927
29045
|
var AnvilParser = class {
|
|
28928
29046
|
/** Lists occupied chunk positions within the region, using local coordinates from 0 to 31. */
|
|
28929
29047
|
static getChunkList(data) {
|
|
28930
29048
|
return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
|
|
28931
29049
|
}
|
|
28932
|
-
/** Decodes all stored chunks. Gzip, zlib, and uncompressed payloads are supported. */
|
|
28933
|
-
static async parse(data) {
|
|
29050
|
+
/** Decodes all stored chunks. Gzip, zlib, LZ4, and uncompressed payloads are supported. */
|
|
29051
|
+
static async parse(data, options = {}) {
|
|
29052
|
+
options.signal?.throwIfAborted();
|
|
28934
29053
|
const bytes = this.bytes(data);
|
|
28935
29054
|
const chunks = [];
|
|
28936
|
-
for (const location of this.locations(bytes))
|
|
29055
|
+
for (const location of this.locations(bytes)) {
|
|
29056
|
+
chunks.push(await this.abortable(this.readChunk(bytes, location, options), options.signal));
|
|
29057
|
+
}
|
|
28937
29058
|
return { chunks };
|
|
28938
29059
|
}
|
|
28939
29060
|
/**
|
|
@@ -28942,13 +29063,28 @@ var AnvilParser = class {
|
|
|
28942
29063
|
* @param localZ - Chunk z within the region, from 0 to 31.
|
|
28943
29064
|
* @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
|
|
28944
29065
|
*/
|
|
28945
|
-
static async parseChunk(data, localX, localZ) {
|
|
29066
|
+
static async parseChunk(data, localX, localZ, options = {}) {
|
|
29067
|
+
options.signal?.throwIfAborted();
|
|
28946
29068
|
if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
|
|
28947
29069
|
throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
|
|
28948
29070
|
}
|
|
28949
29071
|
const bytes = this.bytes(data);
|
|
28950
29072
|
const location = this.locations(bytes).find(({ x, z }) => x === localX && z === localZ);
|
|
28951
|
-
return location ? this.readChunk(bytes, location) : void 0;
|
|
29073
|
+
return location ? this.abortable(this.readChunk(bytes, location, options), options.signal) : void 0;
|
|
29074
|
+
}
|
|
29075
|
+
static async abortable(promise, signal) {
|
|
29076
|
+
if (!signal) return promise;
|
|
29077
|
+
let abort;
|
|
29078
|
+
const cancelled = new Promise((_, reject) => {
|
|
29079
|
+
abort = () => reject(signal.reason);
|
|
29080
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
29081
|
+
if (signal.aborted) abort();
|
|
29082
|
+
});
|
|
29083
|
+
try {
|
|
29084
|
+
return await Promise.race([promise, cancelled]);
|
|
29085
|
+
} finally {
|
|
29086
|
+
signal.removeEventListener("abort", abort);
|
|
29087
|
+
}
|
|
28952
29088
|
}
|
|
28953
29089
|
static bytes(data) {
|
|
28954
29090
|
return data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
@@ -28974,18 +29110,41 @@ var AnvilParser = class {
|
|
|
28974
29110
|
}
|
|
28975
29111
|
return locations;
|
|
28976
29112
|
}
|
|
28977
|
-
static async readChunk(data, location) {
|
|
28978
|
-
|
|
28979
|
-
|
|
28980
|
-
|
|
28981
|
-
|
|
29113
|
+
static async readChunk(data, location, options) {
|
|
29114
|
+
options.signal?.throwIfAborted();
|
|
29115
|
+
const format2 = data[location.offset + 4];
|
|
29116
|
+
const external = (format2 & 128) !== 0;
|
|
29117
|
+
const compression = format2 & 127;
|
|
29118
|
+
if (compression !== 1 && compression !== 2 && compression !== 3 && compression !== 4) {
|
|
29119
|
+
throw new MineRenderError(`Unsupported Anvil compression ${compression}`);
|
|
28982
29120
|
}
|
|
28983
29121
|
let payload = data.subarray(location.offset + 5, location.offset + 4 + location.length);
|
|
28984
|
-
|
|
29122
|
+
let expected;
|
|
29123
|
+
if (external) {
|
|
29124
|
+
if (!options.region || !options.readExternalChunk) {
|
|
29125
|
+
throw new MineRenderError("External Anvil .mcc chunks require region coordinates and readExternalChunk");
|
|
29126
|
+
}
|
|
29127
|
+
if (![options.region.x, options.region.z].every(Number.isSafeInteger)) {
|
|
29128
|
+
throw new RangeError("Anvil region coordinates must be safe integers");
|
|
29129
|
+
}
|
|
29130
|
+
expected = { x: options.region.x * 32 + location.x, z: options.region.z * 32 + location.z };
|
|
29131
|
+
if (![expected.x, expected.z].every(Number.isSafeInteger)) {
|
|
29132
|
+
throw new RangeError("External Anvil chunk coordinates must be safe integers");
|
|
29133
|
+
}
|
|
29134
|
+
const externalData = await options.readExternalChunk(expected.x, expected.z, options.signal);
|
|
29135
|
+
options.signal?.throwIfAborted();
|
|
29136
|
+
if (externalData === void 0) throw new MineRenderError(`External Anvil chunk c.${expected.x}.${expected.z}.mcc is missing`);
|
|
29137
|
+
payload = this.bytes(externalData);
|
|
29138
|
+
}
|
|
29139
|
+
if (compression === 4) {
|
|
29140
|
+
payload = decodeAnvilLz4(payload);
|
|
29141
|
+
} else if (compression !== 3) {
|
|
28985
29142
|
const stream = new Blob([payload]).stream().pipeThrough(new DecompressionStream(compression === 1 ? "gzip" : "deflate"));
|
|
28986
29143
|
payload = new Uint8Array(await new Response(stream).arrayBuffer());
|
|
28987
29144
|
}
|
|
29145
|
+
options.signal?.throwIfAborted();
|
|
28988
29146
|
const nbt = await NBTHelper.fromBuffer(payload, "big");
|
|
29147
|
+
options.signal?.throwIfAborted();
|
|
28989
29148
|
const root = nbt.value;
|
|
28990
29149
|
const level = root.Level?.type === "compound" ? root.Level.value : root;
|
|
28991
29150
|
const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
|
|
@@ -28998,6 +29157,9 @@ var AnvilParser = class {
|
|
|
28998
29157
|
if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
|
|
28999
29158
|
throw new MineRenderError("Anvil chunk coordinates do not match its region location");
|
|
29000
29159
|
}
|
|
29160
|
+
if (expected && (chunk.x !== expected.x || chunk.z !== expected.z)) {
|
|
29161
|
+
throw new MineRenderError(`Anvil chunk coordinates do not match external file c.${expected.x}.${expected.z}.mcc`);
|
|
29162
|
+
}
|
|
29001
29163
|
for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
|
|
29002
29164
|
const y = this.integer(section.Y, "section Y");
|
|
29003
29165
|
if (section.Blocks || section.Data || section.Add) {
|
|
@@ -29129,6 +29291,7 @@ var AnvilWorldSource = class {
|
|
|
29129
29291
|
this.cachedBytes = 0;
|
|
29130
29292
|
this.maxCachedRegions = options.maxCachedRegions ?? 4;
|
|
29131
29293
|
this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
|
|
29294
|
+
this.readExternalChunk = options.readExternalChunk;
|
|
29132
29295
|
for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
|
|
29133
29296
|
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
|
|
29134
29297
|
}
|
|
@@ -29142,7 +29305,11 @@ var AnvilWorldSource = class {
|
|
|
29142
29305
|
const data = await this.getRegion(regionX, regionZ, signal);
|
|
29143
29306
|
signal?.throwIfAborted();
|
|
29144
29307
|
if (!data) return void 0;
|
|
29145
|
-
const chunk = await
|
|
29308
|
+
const chunk = await AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32, {
|
|
29309
|
+
region: { x: regionX, z: regionZ },
|
|
29310
|
+
readExternalChunk: this.readExternalChunk,
|
|
29311
|
+
signal
|
|
29312
|
+
});
|
|
29146
29313
|
signal?.throwIfAborted();
|
|
29147
29314
|
if (chunk && (chunk.x !== x || chunk.z !== z)) {
|
|
29148
29315
|
throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
|
|
@@ -29173,7 +29340,7 @@ var AnvilWorldSource = class {
|
|
|
29173
29340
|
settled: false,
|
|
29174
29341
|
promise: Promise.resolve().then(() => {
|
|
29175
29342
|
controller.signal.throwIfAborted();
|
|
29176
|
-
return this.readRegion(x, z, controller.signal);
|
|
29343
|
+
return abortable2(this.readRegion(x, z, controller.signal), controller.signal);
|
|
29177
29344
|
}).then((data) => {
|
|
29178
29345
|
controller.signal.throwIfAborted();
|
|
29179
29346
|
let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);
|