From 07b1c13aaab455b9f412bb74f40cf25aa62e5078 Mon Sep 17 00:00:00 2001 From: Pix3lPirat3 Date: Fri, 4 Sep 2026 20:47:06 -0700 Subject: [PATCH 1/2] send one protocol-shaped decline Make the hash, result-only, and UUID response forms mutually exclusive so UUID-era clients do not emit a second malformed response. --- lib/plugins/resource_pack.js | 20 +++++--- test/resourcePackTest.js | 88 ++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 test/resourcePackTest.js diff --git a/lib/plugins/resource_pack.js b/lib/plugins/resource_pack.js index fc70ce347a..71984df53f 100644 --- a/lib/plugins/resource_pack.js +++ b/lib/plugins/resource_pack.js @@ -60,11 +60,11 @@ function inject (bot) { }) } else if (bot.supportFeature('resourcePackUsesUUID')) { bot._client.write('resource_pack_receive', { - uuid: latestUUID, + uuid: latestUUID.toString(), result: TEXTURE_PACK_RESULTS.ACCEPTED }) bot._client.write('resource_pack_receive', { - uuid: latestUUID, + uuid: latestUUID.toString(), result: TEXTURE_PACK_RESULTS.SUCCESSFULLY_LOADED }) } else { @@ -78,15 +78,21 @@ function inject (bot) { } function denyResourcePack () { - if (bot.supportFeature('resourcePackUsesUUID')) { + if (bot.supportFeature('resourcePackUsesHash')) { + bot._client.write('resource_pack_receive', { + hash: latestHash, + result: TEXTURE_PACK_RESULTS.DECLINED + }) + } else if (bot.supportFeature('resourcePackUsesUUID')) { + bot._client.write('resource_pack_receive', { + uuid: latestUUID.toString(), + result: TEXTURE_PACK_RESULTS.DECLINED + }) + } else { bot._client.write('resource_pack_receive', { - uuid: latestUUID, result: TEXTURE_PACK_RESULTS.DECLINED }) } - bot._client.write('resource_pack_receive', { - result: TEXTURE_PACK_RESULTS.DECLINED - }) } bot.acceptResourcePack = acceptResourcePack diff --git a/test/resourcePackTest.js b/test/resourcePackTest.js new file mode 100644 index 0000000000..24e4850a20 --- /dev/null +++ b/test/resourcePackTest.js @@ -0,0 +1,88 @@ +/* eslint-env mocha */ + +const assert = require('assert') +const EventEmitter = require('events') +const mc = require('minecraft-protocol') +const minecraftData = require('minecraft-data') +const mineflayer = require('../') +const inject = require('../lib/plugins/resource_pack') + +describe('resource pack plugin', () => { + function createMockBot (supportFeature) { + const writes = [] + const bot = new EventEmitter() + bot._client = new EventEmitter() + bot._client.write = (name, data) => writes.push({ name, data }) + bot.supportFeature = supportFeature + inject(bot) + return { bot, writes } + } + + for (const version of mineflayer.testedVersions) { + const registry = minecraftData(version) + + it(`serializes one correctly shaped decline in ${version}`, () => { + const usesHash = registry.supportFeature('resourcePackUsesHash') + const usesUUID = registry.supportFeature('resourcePackUsesUUID') + const { bot, writes } = createMockBot(registry.supportFeature) + const uuid = '9f41f8d8-5a3e-4ea6-8a4c-404400000001' + + if (usesHash) { + bot._client.emit('resource_pack_send', { url: 'https://example.invalid/pack.zip', hash: 'pack-hash' }) + } else if (usesUUID) { + bot._client.emit('add_resource_pack', { uuid, url: 'https://example.invalid/pack.zip' }) + } + + bot.denyResourcePack() + + assert.strictEqual(writes.length, 1) + assert.strictEqual(writes[0].name, 'resource_pack_receive') + assert.strictEqual(writes[0].data.result, 1) + if (usesHash) { + assert.deepStrictEqual(writes[0].data, { hash: 'pack-hash', result: 1 }) + } else if (usesUUID) { + assert.deepStrictEqual(Object.keys(writes[0].data).sort(), ['result', 'uuid']) + assert.strictEqual(writes[0].data.uuid, uuid) + } else { + assert.deepStrictEqual(writes[0].data, { result: 1 }) + } + + const state = usesUUID ? mc.states.CONFIGURATION : mc.states.PLAY + const serializer = mc.createSerializer({ state, isServer: false, version }) + const packetBuffer = serializer.createPacketBuffer({ + name: writes[0].name, + params: writes[0].data + }) + const deserializer = mc.createDeserializer({ state, isServer: true, version }) + const parsed = deserializer.parsePacketBuffer(packetBuffer).data + assert.strictEqual(parsed.name, 'resource_pack_receive') + assert.strictEqual(parsed.params.result, 1) + if (usesUUID) assert.strictEqual(parsed.params.uuid, uuid) + }) + + if (registry.supportFeature('resourcePackUsesUUID')) { + it(`serializes the resource pack UUID when accepting in ${version}`, () => { + const { bot, writes } = createMockBot(registry.supportFeature) + const uuid = '9f41f8d8-5a3e-4ea6-8a4c-404400000001' + bot._client.emit('add_resource_pack', { uuid, url: 'https://example.invalid/pack.zip' }) + + bot.acceptResourcePack() + + assert.deepStrictEqual(writes.map(write => write.data), [ + { uuid, result: 3 }, + { uuid, result: 0 } + ]) + const serializer = mc.createSerializer({ state: mc.states.CONFIGURATION, isServer: false, version }) + const deserializer = mc.createDeserializer({ state: mc.states.CONFIGURATION, isServer: true, version }) + for (const write of writes) { + const parsed = deserializer.parsePacketBuffer(serializer.createPacketBuffer({ + name: write.name, + params: write.data + })).data + assert.strictEqual(parsed.params.uuid, uuid) + assert.strictEqual(parsed.params.result, write.data.result) + } + }) + } + } +}) From 056fbb143fba7595de39cefe5eccbcf4ba0bd796 Mon Sep 17 00:00:00 2001 From: Pix3lPirat3 Date: Fri, 4 Sep 2026 21:21:00 -0700 Subject: [PATCH 2/2] tweaks from #3842 Appended tweaks from https://github.com/PrismarineJS/mineflayer/pull/3842 --- lib/plugins/resource_pack.js | 65 +++++++----------------- test/resourcePackTest.js | 98 ++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 95 deletions(-) diff --git a/lib/plugins/resource_pack.js b/lib/plugins/resource_pack.js index 71984df53f..cd8c00d98b 100644 --- a/lib/plugins/resource_pack.js +++ b/lib/plugins/resource_pack.js @@ -3,7 +3,6 @@ const UUID = require('uuid-1345') module.exports = inject function inject (bot) { - let uuid let latestHash let latestUUID let activeResourcePacks = {} @@ -17,7 +16,7 @@ function inject (bot) { bot._client.on('add_resource_pack', (data) => { // Emits the same as resource_pack_send but sends uuid rather than hash because that's how active packs are tracked const uuid = new UUID(data.uuid) // Adding the pack to a set by uuid - latestUUID = uuid + latestUUID = data.uuid activeResourcePacks[uuid] = data.url bot.emit('resourcePack', data.url, uuid) @@ -38,61 +37,35 @@ function inject (bot) { }) bot._client.on('resource_pack_send', (data) => { + latestUUID = data.uuid + latestHash = data.hash if (bot.supportFeature('resourcePackUsesUUID')) { - uuid = new UUID(data.uuid) + const uuid = new UUID(data.uuid) bot.emit('resourcePack', uuid, data.url) - latestUUID = uuid } else { bot.emit('resourcePack', data.url, data.hash) - latestHash = data.hash } }) function acceptResourcePack () { - if (bot.supportFeature('resourcePackUsesHash')) { - bot._client.write('resource_pack_receive', { - result: TEXTURE_PACK_RESULTS.ACCEPTED, - hash: latestHash - }) - bot._client.write('resource_pack_receive', { - result: TEXTURE_PACK_RESULTS.SUCCESSFULLY_LOADED, - hash: latestHash - }) - } else if (bot.supportFeature('resourcePackUsesUUID')) { - bot._client.write('resource_pack_receive', { - uuid: latestUUID.toString(), - result: TEXTURE_PACK_RESULTS.ACCEPTED - }) - bot._client.write('resource_pack_receive', { - uuid: latestUUID.toString(), - result: TEXTURE_PACK_RESULTS.SUCCESSFULLY_LOADED - }) - } else { - bot._client.write('resource_pack_receive', { - result: TEXTURE_PACK_RESULTS.ACCEPTED - }) - bot._client.write('resource_pack_receive', { - result: TEXTURE_PACK_RESULTS.SUCCESSFULLY_LOADED - }) - } + bot._client.write('resource_pack_receive', { + uuid: latestUUID, + hash: latestHash, + result: TEXTURE_PACK_RESULTS.ACCEPTED + }) + bot._client.write('resource_pack_receive', { + uuid: latestUUID, + hash: latestHash, + result: TEXTURE_PACK_RESULTS.SUCCESSFULLY_LOADED + }) } function denyResourcePack () { - if (bot.supportFeature('resourcePackUsesHash')) { - bot._client.write('resource_pack_receive', { - hash: latestHash, - result: TEXTURE_PACK_RESULTS.DECLINED - }) - } else if (bot.supportFeature('resourcePackUsesUUID')) { - bot._client.write('resource_pack_receive', { - uuid: latestUUID.toString(), - result: TEXTURE_PACK_RESULTS.DECLINED - }) - } else { - bot._client.write('resource_pack_receive', { - result: TEXTURE_PACK_RESULTS.DECLINED - }) - } + bot._client.write('resource_pack_receive', { + uuid: latestUUID, + hash: latestHash, + result: TEXTURE_PACK_RESULTS.DECLINED + }) } bot.acceptResourcePack = acceptResourcePack diff --git a/test/resourcePackTest.js b/test/resourcePackTest.js index 24e4850a20..557efae773 100644 --- a/test/resourcePackTest.js +++ b/test/resourcePackTest.js @@ -8,6 +8,10 @@ const mineflayer = require('../') const inject = require('../lib/plugins/resource_pack') describe('resource pack plugin', () => { + const uuid = '9f41f8d8-5a3e-4ea6-8a4c-404400000001' + const url = 'https://example.invalid/pack.zip' + const hash = 'pack-hash' + function createMockBot (supportFeature) { const writes = [] const bot = new EventEmitter() @@ -18,6 +22,31 @@ describe('resource pack plugin', () => { return { bot, writes } } + function emitResourcePack (bot, usesUUID) { + if (usesUUID) { + bot._client.emit('add_resource_pack', { uuid, url }) + } else { + bot._client.emit('resource_pack_send', { url, hash }) + } + } + + function serializeAndParse (version, usesUUID, write) { + const state = usesUUID ? mc.states.CONFIGURATION : mc.states.PLAY + const serializer = mc.createSerializer({ state, isServer: false, version }) + const deserializer = mc.createDeserializer({ state, isServer: true, version }) + return deserializer.parsePacketBuffer(serializer.createPacketBuffer({ + name: write.name, + params: write.data + })).data + } + + function assertResponse (parsed, result, usesHash, usesUUID) { + assert.strictEqual(parsed.name, 'resource_pack_receive') + assert.strictEqual(parsed.params.result, result) + if (usesHash) assert.strictEqual(parsed.params.hash, hash) + if (usesUUID) assert.strictEqual(parsed.params.uuid, uuid) + } + for (const version of mineflayer.testedVersions) { const registry = minecraftData(version) @@ -25,64 +54,35 @@ describe('resource pack plugin', () => { const usesHash = registry.supportFeature('resourcePackUsesHash') const usesUUID = registry.supportFeature('resourcePackUsesUUID') const { bot, writes } = createMockBot(registry.supportFeature) - const uuid = '9f41f8d8-5a3e-4ea6-8a4c-404400000001' - if (usesHash) { - bot._client.emit('resource_pack_send', { url: 'https://example.invalid/pack.zip', hash: 'pack-hash' }) - } else if (usesUUID) { - bot._client.emit('add_resource_pack', { uuid, url: 'https://example.invalid/pack.zip' }) - } - - bot.denyResourcePack() + bot.once('resourcePack', () => bot.denyResourcePack()) + emitResourcePack(bot, usesUUID) assert.strictEqual(writes.length, 1) assert.strictEqual(writes[0].name, 'resource_pack_receive') assert.strictEqual(writes[0].data.result, 1) - if (usesHash) { - assert.deepStrictEqual(writes[0].data, { hash: 'pack-hash', result: 1 }) - } else if (usesUUID) { - assert.deepStrictEqual(Object.keys(writes[0].data).sort(), ['result', 'uuid']) - assert.strictEqual(writes[0].data.uuid, uuid) - } else { - assert.deepStrictEqual(writes[0].data, { result: 1 }) - } + assert.strictEqual(writes[0].data.uuid, usesUUID ? uuid : undefined) + assert.strictEqual(writes[0].data.hash, usesUUID ? undefined : hash) - const state = usesUUID ? mc.states.CONFIGURATION : mc.states.PLAY - const serializer = mc.createSerializer({ state, isServer: false, version }) - const packetBuffer = serializer.createPacketBuffer({ - name: writes[0].name, - params: writes[0].data - }) - const deserializer = mc.createDeserializer({ state, isServer: true, version }) - const parsed = deserializer.parsePacketBuffer(packetBuffer).data - assert.strictEqual(parsed.name, 'resource_pack_receive') - assert.strictEqual(parsed.params.result, 1) - if (usesUUID) assert.strictEqual(parsed.params.uuid, uuid) + const parsed = serializeAndParse(version, usesUUID, writes[0]) + assertResponse(parsed, 1, usesHash, usesUUID) }) - if (registry.supportFeature('resourcePackUsesUUID')) { - it(`serializes the resource pack UUID when accepting in ${version}`, () => { - const { bot, writes } = createMockBot(registry.supportFeature) - const uuid = '9f41f8d8-5a3e-4ea6-8a4c-404400000001' - bot._client.emit('add_resource_pack', { uuid, url: 'https://example.invalid/pack.zip' }) + it(`serializes both acceptance responses in ${version}`, () => { + const usesHash = registry.supportFeature('resourcePackUsesHash') + const usesUUID = registry.supportFeature('resourcePackUsesUUID') + const { bot, writes } = createMockBot(registry.supportFeature) - bot.acceptResourcePack() + bot.once('resourcePack', () => bot.acceptResourcePack()) + emitResourcePack(bot, usesUUID) - assert.deepStrictEqual(writes.map(write => write.data), [ - { uuid, result: 3 }, - { uuid, result: 0 } - ]) - const serializer = mc.createSerializer({ state: mc.states.CONFIGURATION, isServer: false, version }) - const deserializer = mc.createDeserializer({ state: mc.states.CONFIGURATION, isServer: true, version }) - for (const write of writes) { - const parsed = deserializer.parsePacketBuffer(serializer.createPacketBuffer({ - name: write.name, - params: write.data - })).data - assert.strictEqual(parsed.params.uuid, uuid) - assert.strictEqual(parsed.params.result, write.data.result) - } - }) - } + assert.deepStrictEqual(writes.map(write => write.data.result), [3, 0]) + for (const write of writes) { + assert.strictEqual(write.data.uuid, usesUUID ? uuid : undefined) + assert.strictEqual(write.data.hash, usesUUID ? undefined : hash) + const parsed = serializeAndParse(version, usesUUID, write) + assertResponse(parsed, write.data.result, usesHash, usesUUID) + } + }) } })