diff --git a/data/new/compile.js b/data/new/compile.js index 6df30d9..7ffca0e 100644 --- a/data/new/compile.js +++ b/data/new/compile.js @@ -18,9 +18,9 @@ fs.unlinkSync('./protocol.json') //remove temp file function createProtocol() { const compiler = new ProtoDefCompiler() const protocol = require('../newproto.json').types - compiler.addTypesToCompile(protocol) compiler.addTypes(require('../../src/datatypes/compiler-minecraft')) compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) + compiler.addTypesToCompile(protocol) fs.writeFileSync('../read.js', 'module.exports = ' + compiler.readCompiler.generate()) fs.writeFileSync('../write.js', 'module.exports = ' + compiler.writeCompiler.generate()) diff --git a/data/new/proto.yml b/data/new/proto.yml index 9727a76..b24839e 100644 --- a/data/new/proto.yml +++ b/data/new/proto.yml @@ -1013,29 +1013,83 @@ packet_show_credits: runtime_entity_id: varint status: zigzag32 +# This packet sends a list of commands to the client. Commands can have +# arguments, and some of those arguments can have 'enum' values, which are a list of possible +# values for the argument. The serialization is rather complex and involves palettes like chunks. +## In bedrock-protocol, listen to on('client.commands') for a simpler representation packet_available_commands: !id: 0x4c !bound: client - # enum_values: string[]varint - # suffixes: string[]varint - # enums: []varint - # name: string - # # The length of the array below - # values_len: varint - # # Not read from stream: instead calculated from the `values_len` field - # # If the values_len < 0xff => byte - # # If the values_len < 0xffff => short - # # If the values_len < 0xffffff => int - # enum_typex: u8 => - # 0: byte - # 1: short - # 2: int - # valuex: []varint - # # S: bool - # ix: ../enum_typex? - # if byte: u8 - # if short: lu16 - # if int: lu32 + # Here all the enum values for all of the possible commands are stored to one array palette + enum_values: string[]varint + # Integer paramaters may sometimes have a prefix, such as the XP command: + # /xp [player: target] <- here, the xp command gives experience points + # /xp L [player: target] <- here, the xp command gives experience levels + # This is the palette of suffixes + suffixes: string[]varint + # The list of enum objects + enums: []varint + # The name of the enum + name: string + # The length of the array below + values_len: varint + # Not read from stream: instead calculated from the `values_len` field + # If the values_len < 0xff => byte + # If the values_len < 0xffff => short + # If the values_len < 0xffffff => int + _enum_type: '["enum_size_based_on_values_len"]' + # The values in the enum + values: []$values_len + # The indexes to value in the palette + _: ../_enum_type? + if byte: u8 + if short: lu16 + if int: lu32 + command_data: []varint + name: string + description: string + flags: u8 + permission_level: u8 + alias: li32 + # The list of overload paramaters for this command + overloads: []varint + # Each of the paramaters gets an array of posible overloads + _: []varint + # The name of the paramater shown to the user (the `amount` in `/xp `) + paramater_name: string + # Bitfield: If FLAG_ENUM is set, the lower 16 bits act as an index to enums array + # If FLAG_SUFFIX is set, the lower 16 bits act as an index to the suffix palette + # and the client interperts the type as just `int` as in the example above + paramater_type: li32 + # Is this paramater required? + optional: bool + # Additinal options for this command (thanks macroshaft...) + flags: CommandFlags + # There are two types of enums: static enums which cannot be changed after sending AvaliableCommands, + # (unless you resend the whole packet) and 'soft' or 'dynamic' enums like below which is an array + # that can be updated with the UpdateSoftEnum packet + dynamic_enums: []varint + name: string + values: string[]varint + enum_constraints: []varint + value_index: li32 + enum_index: li32 + constraints: []varint + constraint: u8 => + 0: cheats_enabled + +# ParamOptionCollapseEnum specifies if the enum (only if the Type is actually an enum type. If not, +# setting this to true has no effect) should be collapsed. This means that the options of the enum are +# never shown in the actual usage of the command, but only as auto-completion, like it automatically does +# with enums that have a big amount of options. To illustrate, it can make +# <$Name: bool>. +CommandFlags: [ "bitfield", [ + { "name": "unused", "size": 1, "signed": false }, + { "name": "collapse_enum", "size": 1, "signed": false }, + { "name": "has_semantic_constraint", "size": 1, "signed": false } +]] + +# enum_size_based_on_values_len: native # CommandRequest is sent by the client to request the execution of a server-side command. Although some # servers support sending commands using the Text packet, this packet is guaranteed to have the correct diff --git a/data/newproto.json b/data/newproto.json index 7a6f93e..ead075d 100644 --- a/data/newproto.json +++ b/data/newproto.json @@ -4518,7 +4518,223 @@ ], "packet_available_commands": [ "container", - [] + [ + { + "name": "enum_values", + "type": [ + "array", + { + "countType": "varint", + "type": "string" + } + ] + }, + { + "name": "suffixes", + "type": [ + "array", + { + "countType": "varint", + "type": "string" + } + ] + }, + { + "name": "enums", + "type": [ + "array", + { + "countType": "varint", + "type": [ + "container", + [ + { + "name": "name", + "type": "string" + }, + { + "name": "values_len", + "type": "varint" + }, + { + "name": "_enum_type", + "type": [ + "enum_size_based_on_values_len" + ] + }, + { + "name": "values", + "type": [ + "array", + { + "count": "values_len", + "type": [ + "switch", + { + "compareTo": "../_enum_type", + "fields": { + "byte": "u8", + "short": "lu16", + "int": "lu32" + }, + "default": "void" + } + ] + } + ] + } + ] + ] + } + ] + }, + { + "name": "command_data", + "type": [ + "array", + { + "countType": "varint", + "type": [ + "container", + [ + { + "name": "name", + "type": "string" + }, + { + "name": "description", + "type": "string" + }, + { + "name": "flags", + "type": "u8" + }, + { + "name": "permission_level", + "type": "u8" + }, + { + "name": "alias", + "type": "li32" + }, + { + "name": "overloads", + "type": [ + "array", + { + "countType": "varint", + "type": [ + "array", + { + "countType": "varint", + "type": [ + "container", + [ + { + "name": "paramater_name", + "type": "string" + }, + { + "name": "paramater_type", + "type": "li32" + }, + { + "name": "optional", + "type": "bool" + }, + { + "name": "flags", + "type": "CommandFlags" + } + ] + ] + } + ] + } + ] + } + ] + ] + } + ] + }, + { + "name": "dynamic_enums", + "type": [ + "array", + { + "countType": "varint", + "type": [ + "container", + [ + { + "name": "name", + "type": "string" + }, + { + "name": "values", + "type": [ + "array", + { + "countType": "varint", + "type": "string" + } + ] + } + ] + ] + } + ] + }, + { + "name": "enum_constraints", + "type": [ + "array", + { + "countType": "varint", + "type": [ + "container", + [ + { + "name": "value_index", + "type": "li32" + }, + { + "name": "enum_index", + "type": "li32" + }, + { + "name": "constraints", + "type": [ + "array", + { + "countType": "varint", + "type": [ + "container", + [ + { + "name": "constraint", + "type": [ + "mapper", + { + "type": "u8", + "mappings": { + "0": "cheats_enabled" + } + } + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] ], "packet_command_request": [ "container", @@ -5546,6 +5762,26 @@ { "countType": "li32" } + ], + "CommandFlags": [ + "bitfield", + [ + { + "name": "unused", + "size": 1, + "signed": false + }, + { + "name": "collapse_enum", + "size": 1, + "signed": false + }, + { + "name": "has_semantic_constraint", + "size": 1, + "signed": false + } + ] ] } } \ No newline at end of file diff --git a/src/datatypes/compiler-minecraft.js b/src/datatypes/compiler-minecraft.js index 7dc6ea4..7130749 100644 --- a/src/datatypes/compiler-minecraft.js +++ b/src/datatypes/compiler-minecraft.js @@ -1,39 +1,84 @@ const UUID = require('uuid-1345') const minecraft = require('./minecraft') -module.exports = { - Read: { - uuid: ['native', (buffer, offset) => { - return { - value: UUID.stringify(buffer.slice(offset, 16 + offset)), - size: 16 - } - }], - restBuffer: ['native', (buffer, offset) => { - return { - value: buffer.slice(offset), - size: buffer.length - offset - } - }], - nbt: ['native', minecraft.nbt[0]] - }, - Write: { - uuid: ['native', (value, buffer, offset) => { - const buf = UUID.parse(value) - buf.copy(buffer, offset) - return offset + 16 - }], - restBuffer: ['native', (value, buffer, offset) => { - value.copy(buffer, offset) - return offset + value.length - }], - nbt: ['native', minecraft.nbt[1]] - }, - SizeOf: { - uuid: ['native', 16], - restBuffer: ['native', (value) => { - return value.length - }], - nbt: ['native', minecraft.nbt[2]] +const Read = {} +const Write = {} +const SizeOf = {} + +/** + * UUIDs + */ +Read.uuid = ['native', (buffer, offset) => { + return { + value: UUID.stringify(buffer.slice(offset, 16 + offset)), + size: 16 } +}] +Write.uuid = ['native', (value, buffer, offset) => { + const buf = UUID.parse(value) + buf.copy(buffer, offset) + return offset + 16 +}] +SizeOf.uuid = ['native', 16] + +/** + * Rest of buffer + */ +Read.restBuffer = ['native', (buffer, offset) => { + return { + value: buffer.slice(offset), + size: buffer.length - offset + } +}] +Write.restBuffer = ['native', (value, buffer, offset) => { + value.copy(buffer, offset) + return offset + value.length +}] +SizeOf.restBuffer = ['native', (value) => { + return value.length +}] + +/** + * NBT + */ +Read.nbt = ['native', minecraft.nbt[0]] +Write.nbt = ['native', minecraft.nbt[1]] +SizeOf.nbt = ['native', minecraft.nbt[2]] + +/** + * Command Packet + * - used for determining the size of the following enum + */ +Read.enum_size_based_on_values_len = ['parametrizable', (compiler, array) => { + return compiler.wrapCode(js(() => { + if (values_len <= 0xff) return { value: 'byte', size: 0 } + if (values_len <= 0xffff) return { value: 'short', size: 0 } + if (values_len <= 0xffffff) return { value: 'int', size: 0 } + })) +}] +Write.enum_size_based_on_values_len = ['parametrizable', (compiler, array) => { + return str(() => { + if (value.values_len <= 0xff) _enum_type = 'byte' + else if (value.values_len <= 0xffff) _enum_type = 'short' + else if (value.values_len <= 0xffffff) _enum_type = 'int' + return offset + }) +}] +SizeOf.enum_size_based_on_values_len = ['parametrizable', (compiler, array) => { + return str(() => { + if (value.values_len <= 0xff) _enum_type = 'byte' + else if (value.values_len <= 0xffff) _enum_type = 'short' + else if (value.values_len <= 0xffffff) _enum_type = 'int' + return 0 + }) +}] + +function js(fn) { + return fn.toString().split('\n').slice(1, -1).join('\n').trim() } + +function str(fn) { + return fn.toString() + ')();(()=>{}' +} + +module.exports = { Read, Write, SizeOf } \ No newline at end of file diff --git a/test/serialization.js b/test/serialization.js index c7588ca..a15441f 100644 --- a/test/serialization.js +++ b/test/serialization.js @@ -110,8 +110,61 @@ function test() { console.log(JSON.stringify(d)) } + async function availableCommands() { + var avaliable = { + enum_values: ['true', 'false'], + suffixes: ['L'], + enums: [ + { + name: 'Boolean', + values_len: 2, + values: [0, 1] + } + ], + command_data: [ + { + name: 'give', + description: 'Gives you items', + flags: 0, + permission_level: 0, + alias: -1, + overloads: [ + [ + { paramater_name: 'player id', paramater_type: 2, optional: false, flags: { "collapse_enum": 1 } } + ], + [ + { paramater_name: 'item id', paramater_type: 2, optional: false, flags: 0 }, + ] + ], + }, + { + name: 'xp', + description: 'Gives you xp', + flags: 0, permission_level: 0, alias: -1, + overloads: [ + [{ paramater_name: 'player id', paramater_type: 2, optional: false, flags: 0 }] + ] + } + ], + enum_constraints: [ + { + value_index: 2, enum_index: 3, constraints: [ + { constraint: 'cheats_enabled' } + ] + }, + ], + dynamic_enums: [ + { name: 'Hello', values: ['yolo', 'yee'] } + ] + } - creativeTst() + var s = write('available_commands', avaliable) + var d = read(s).data.params + console.log(JSON.stringify(d, null, 2)) + } + + // creativeTst() + availableCommands() } if (!module.parent) {