From 1aca229d1f829ccccc70fa3fc419f5e4fd82f7b1 Mon Sep 17 00:00:00 2001 From: u9g Date: Sun, 6 Sep 2026 20:26:05 -0400 Subject: [PATCH] fix: send a command with no signable argument as chat_command _signedChat chose chat_command_signed whenever the client had profile keys and a chat session, regardless of the command. The vanilla client picks the packet per command: ClientPacketListener.sendCommand sends the unsigned ServerboundChatCommandPacket when SignableCommand.of(...) .arguments() is empty, and only sends the signed packet when there is an argument to sign. Every argumentless command (/login, /list, and any command the server's command tree does not mark as a message argument) went out on the signed packet with an empty signature list. The packet now follows the signatures actually produced. --- src/client/chat.js | 6 ++-- test/declareCommandsTest.js | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/client/chat.js b/src/client/chat.js index 579aac0a..0012e216 100644 --- a/src/client/chat.js +++ b/src/client/chat.js @@ -413,16 +413,18 @@ module.exports = function (client, options) { if (mcData.supportFeature('useChatSessions')) { // 1.19.3+ const { acknowledged, acknowledgements } = getAcknowledgements() const canSign = client.profileKeys && client._session + const argumentSignatures = canSign ? signaturesForCommand(command, options.timestamp, options.salt, options.preview, acknowledgements) : [] const chatPacket = { command, timestamp: options.timestamp, salt: options.salt, - argumentSignatures: canSign ? signaturesForCommand(command, options.timestamp, options.salt, options.preview, acknowledgements) : [], + argumentSignatures, messageCount: client._lastSeenMessages.pending, checksum: computeChatChecksum(client._lastSeenMessages), // 1.21.5+ acknowledged } - client.write((mcData.supportFeature('seperateSignedChatCommandPacket') && canSign) ? 'chat_command_signed' : 'chat_command', chatPacket) + // A command with nothing to sign goes as the unsigned chat_command whether or not the client can sign. + client.write((mcData.supportFeature('seperateSignedChatCommandPacket') && argumentSignatures.length > 0) ? 'chat_command_signed' : 'chat_command', chatPacket) client._lastSeenMessages.pending = 0 } else { client.write('chat_command', { diff --git a/test/declareCommandsTest.js b/test/declareCommandsTest.js index 893bb26e..c83b5690 100644 --- a/test/declareCommandsTest.js +++ b/test/declareCommandsTest.js @@ -54,4 +54,61 @@ describe('declare_commands handling', () => { assert.strictEqual(writes[0].name, 'chat_command_signed') assert.deepStrictEqual(writes[0].data.argumentSignatures.map(sig => sig.argumentName), ['message']) }) + + it('sends a command with no signable argument as the unsigned packet', () => { + const client = new EventEmitter() + client.version = '26.1' + client.verifyMessage = () => true + client.profileKeys = true + client._session = { uuid: '00000000-0000-0000-0000-000000000000' } + + const writes = [] + client.write = (name, data) => writes.push({ name, data }) + + injectChatPlugin(client, {}) + client.signMessage = () => Buffer.from([1]) + + client.emit('declare_commands', { + rootIndex: 0, + nodes: [ + { children: [1] }, + { children: [2], extraNodeData: { name: 'msg' } }, + { children: [], extraNodeData: { name: 'message', parser: 'minecraft:message' } } + ] + }) + + client._signedChat('/login hunter2', { timestamp: 1n, salt: 1n }) + + assert.strictEqual(writes.length, 1) + assert.strictEqual(writes[0].name, 'chat_command') + assert.deepStrictEqual(writes[0].data.argumentSignatures, []) + }) + + it('sends a known command without arguments as the unsigned packet', () => { + const client = new EventEmitter() + client.version = '26.1' + client.verifyMessage = () => true + client.profileKeys = true + client._session = { uuid: '00000000-0000-0000-0000-000000000000' } + + const writes = [] + client.write = (name, data) => writes.push({ name, data }) + + injectChatPlugin(client, {}) + client.signMessage = () => Buffer.from([1]) + + client.emit('declare_commands', { + rootIndex: 0, + nodes: [ + { children: [1] }, + { children: [2], extraNodeData: { name: 'msg' } }, + { children: [], extraNodeData: { name: 'message', parser: 'minecraft:message' } } + ] + }) + + client._signedChat('/msg', { timestamp: 1n, salt: 1n }) + + assert.strictEqual(writes.length, 1) + assert.strictEqual(writes[0].name, 'chat_command') + }) })