Skip to content

Commit cbafadb

Browse files
rom1504claude
andcommitted
Fix zlib crashes on Node 24: use sync zlib everywhere
Async zlib (deflate, unzip, gunzip) creates internal Zlib C++ handles that can fire onerror asynchronously, becoming uncaught exceptions on Node 24. Sync versions (deflateSync, unzipSync) complete immediately with no lingering handles. Each call processes one small packet, so sync is safe in the Transform._transform context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dfecf1e commit cbafadb

4 files changed

Lines changed: 30 additions & 19 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"node-rsa": "^0.4.2",
5959
"prismarine-auth": "^2.2.0",
6060
"prismarine-chat": "^1.10.0",
61-
"prismarine-nbt": "^2.5.0",
61+
"prismarine-nbt": "PrismarineJS/prismarine-nbt#fix-zlib-node24",
6262
"prismarine-realms": "^1.2.0",
6363
"protodef": "^1.17.0",
6464
"readable-stream": "^4.1.0",

src/client.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,15 @@ class Client extends EventEmitter {
224224
setCompressionThreshold (threshold) {
225225
if (this.compressor == null) {
226226
this.compressor = compression.createCompressor(threshold)
227-
this.compressor.on('error', (err) => this.emit('error', err))
227+
this.compressor.on('error', (err) => {
228+
if (!this.ended) this.emit('error', err)
229+
})
228230
this.serializer.unpipe(this.framer)
229231
this.serializer.pipe(this.compressor).pipe(this.framer)
230232
this.decompressor = compression.createDecompressor(threshold, this.hideErrors)
231-
this.decompressor.on('error', (err) => this.emit('error', err))
233+
this.decompressor.on('error', (err) => {
234+
if (!this.ended) this.emit('error', err)
235+
})
232236
this.splitter.unpipe(this.deserializer)
233237
this.splitter.pipe(this.decompressor).pipe(this.deserializer)
234238
} else {

src/datatypes/minecraft.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ function readCompressedNbt (buffer, offset) {
5656

5757
const compressedNbt = buffer.slice(offset + 2, offset + 2 + length)
5858

59-
const nbtBuffer = zlib.gunzipSync(compressedNbt) // TODO: async
59+
let nbtBuffer
60+
try {
61+
nbtBuffer = zlib.gunzipSync(compressedNbt) // TODO: async
62+
} catch (err) {
63+
throw new PartialReadError('zlib decompress failed: ' + err.message)
64+
}
6065

6166
const results = nbt.proto.read(nbtBuffer, 0, 'nbt')
6267
return {

src/transforms/compression.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ class Compressor extends Transform {
2020

2121
_transform (chunk, enc, cb) {
2222
if (chunk.length >= this.compressionThreshold) {
23-
zlib.deflate(chunk, (err, newChunk) => {
24-
if (err) { return cb(err) }
23+
try {
24+
const newChunk = zlib.deflateSync(chunk)
2525
const buf = Buffer.alloc(sizeOfVarInt(chunk.length) + newChunk.length)
2626
const offset = writeVarInt(chunk.length, buf, 0)
2727
newChunk.copy(buf, offset)
2828
this.push(buf)
2929
return cb()
30-
})
30+
} catch (err) {
31+
return cb(err)
32+
}
3133
} else {
3234
const buf = Buffer.alloc(sizeOfVarInt(0) + chunk.length)
3335
const offset = writeVarInt(0, buf, 0)
@@ -52,23 +54,23 @@ class Decompressor extends Transform {
5254
this.push(chunk.slice(size))
5355
return cb()
5456
} else {
55-
zlib.unzip(chunk.slice(size), { finishFlush: 2 /* Z_SYNC_FLUSH = 2, but when using Browserify/Webpack it doesn't exist */ }, (err, newBuf) => { /** Fix by lefela4. */
56-
if (err) {
57-
if (!this.hideErrors) {
58-
console.error('problem inflating chunk')
59-
console.error('uncompressed length ' + value)
60-
console.error('compressed length ' + chunk.length)
61-
console.error('hex ' + chunk.toString('hex'))
62-
console.log(err)
63-
}
64-
return cb()
65-
}
57+
try {
58+
const newBuf = zlib.unzipSync(chunk.slice(size), { finishFlush: 2 })
6659
if (newBuf.length !== value && !this.hideErrors) {
6760
console.error('uncompressed length should be ' + value + ' but is ' + newBuf.length)
6861
}
6962
this.push(newBuf)
7063
return cb()
71-
})
64+
} catch (err) {
65+
if (!this.hideErrors) {
66+
console.error('problem inflating chunk')
67+
console.error('uncompressed length ' + value)
68+
console.error('compressed length ' + chunk.length)
69+
console.error('hex ' + chunk.toString('hex'))
70+
console.log(err)
71+
}
72+
return cb()
73+
}
7274
}
7375
}
7476
}

0 commit comments

Comments
 (0)