diff --git a/data/new/compile.js b/data/new/compile.js index 2800ba8..6df30d9 100644 --- a/data/new/compile.js +++ b/data/new/compile.js @@ -1,4 +1,5 @@ const fs = require('fs') +const { ProtoDefCompiler } = require('protodef').Compiler let compile try { @@ -11,5 +12,23 @@ if (compile) { compile('./proto.yml', 'protocol.json') } -fs.writeFileSync( '../newproto.json', JSON.stringify({ types: require('./protocol.json') }, null, 2) ) -fs.unlinkSync('./protocol.json') //remove temp file \ No newline at end of file +fs.writeFileSync('../newproto.json', JSON.stringify({ types: require('./protocol.json') }, null, 2)) +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')) + + fs.writeFileSync('../read.js', 'module.exports = ' + compiler.readCompiler.generate()) + fs.writeFileSync('../write.js', 'module.exports = ' + compiler.writeCompiler.generate()) + fs.writeFileSync('../size.js', 'module.exports = ' + compiler.sizeOfCompiler.generate()) + + const compiledProto = compiler.compileProtoDefSync() + return compiledProto +} + +console.log('Generating JS...') +createProtocol() \ No newline at end of file diff --git a/data/new/types.yaml b/data/new/types.yaml index 6e32afe..2168098 100644 --- a/data/new/types.yaml +++ b/data/new/types.yaml @@ -1,9 +1,9 @@ -!StartDocs: Types +# !StartDocs: Types BehaviourPackInfos: []li16 uuid: string version: string - size: lu64 + length: lu64 content_key: string sub_pack_name: string content_identity: string @@ -12,7 +12,7 @@ BehaviourPackInfos: []li16 TexturePackInfos: []li16 uuid: string version: string - size: lu64 + length: lu64 content_key: string sub_pack_name: string content_identity: string @@ -71,11 +71,11 @@ Item: default: auxiliary_value: zigzag32 has_nbt: lu16 => - 0xffff: '1' - 0x0000: '0' - _: has_nbt? - if 1: - nbt_version: u8 + 0xffff: 'true' + 0x0000: 'false' + nbt: has_nbt? + if true: + version: u8 nbt: nbt default: void can_place_on: string[]zigzag32 @@ -150,10 +150,10 @@ BlockCoordinates: # mojang... z: zigzag32 PlayerAttributes: []varint - min: lf32 - max: lf32 - current: lf32 - default: lf32 + min_value: lf32 + max_value: lf32 + current_value: lf32 + default_value: lf32 name: string Transaction: @@ -360,11 +360,10 @@ ScoreEntries: 1: player 2: entity 3: fake_player - _: entry_type? - if player or entity: - entity_unique_id: zigzag64 - if fake_player: - custom_name: string + entity_unique_id: entry_type? + if player or entity: zigzag64 + custom_name: entry_type? + if fake_player: string ScoreboardIdentityEntries: type: i8 => diff --git a/data/newproto.json b/data/newproto.json index 5424284..e520a62 100644 --- a/data/newproto.json +++ b/data/newproto.json @@ -25,7 +25,7 @@ "type": "string" }, { - "name": "size", + "name": "length", "type": "lu64" }, { @@ -64,7 +64,7 @@ "type": "string" }, { - "name": "size", + "name": "length", "type": "lu64" }, { @@ -281,24 +281,24 @@ { "type": "lu16", "mappings": { - "0": "0", - "65535": "1" + "0": "false", + "65535": "true" } } ] }, { - "anon": true, + "name": "nbt", "type": [ "switch", { "compareTo": "has_nbt", "fields": { - "1": [ + "true": [ "container", [ { - "name": "nbt_version", + "name": "version", "type": "u8" }, { @@ -431,6 +431,10 @@ "type": [ "container", [ + { + "name": "key", + "type": "varint" + }, { "name": "type", "type": [ @@ -576,19 +580,19 @@ "container", [ { - "name": "min", + "name": "min_value", "type": "lf32" }, { - "name": "max", + "name": "max_value", "type": "lf32" }, { - "name": "current", + "name": "current_value", "type": "lf32" }, { - "name": "default", + "name": "default_value", "type": "lf32" }, { @@ -771,7 +775,7 @@ "type": [ "switch", { - "compareTo": "../has_network_ids", + "compareTo": "has_network_ids", "fields": { "true": "zigzag32" }, @@ -1386,7 +1390,7 @@ "type": [ "switch", { - "compareTo": "../type", + "compareTo": "type", "fields": { "add": [ "container", @@ -1512,7 +1516,7 @@ "type": [ "switch", { - "compareTo": "../type", + "compareTo": "type", "fields": { "remove": [ "container", @@ -1532,39 +1536,27 @@ ] }, { - "anon": true, + "name": "entity_unique_id", "type": [ "switch", { "compareTo": "entry_type", "fields": { - "player": [ - "container", - [ - { - "name": "entity_unique_id", - "type": "zigzag64" - } - ] - ], - "entity": [ - "container", - [ - { - "name": "entity_unique_id", - "type": "zigzag64" - } - ] - ], - "fake_player": [ - "container", - [ - { - "name": "custom_name", - "type": "string" - } - ] - ] + "player": "zigzag64", + "entity": "zigzag64" + }, + "default": "void" + } + ] + }, + { + "name": "custom_name", + "type": [ + "switch", + { + "compareTo": "entry_type", + "fields": { + "fake_player": "string" }, "default": "void" } @@ -1618,7 +1610,7 @@ "type": [ "switch", { - "compareTo": "../type", + "compareTo": "type", "fields": { "TYPE_REGISTER_IDENTITY": "zigzag64" }, diff --git a/package.json b/package.json index 6c7aa73..30346b0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "jwt-simple": "^0.5.6", "lodash.merge": "^4.4.0", "prismarine-nbt": "github:extremeheat/prismarine-nbt#le", - "protodef": "github:extremeheat/node-protodef#big", + "protodef": "github:extremeheat/node-protodef#compiler", "raknet": "git+https://github.com/mhsjlw/node-raknet.git#master", "uuid-1345": "^0.99.7" }, diff --git a/src/datatypes/compiler-minecraft.js b/src/datatypes/compiler-minecraft.js new file mode 100644 index 0000000..f8fa767 --- /dev/null +++ b/src/datatypes/compiler-minecraft.js @@ -0,0 +1,39 @@ +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]] + } +} diff --git a/src/options.js b/src/options.js new file mode 100644 index 0000000..dc329ea --- /dev/null +++ b/src/options.js @@ -0,0 +1,12 @@ +// Minimum supported version (< will be kicked) +const MIN_VERSION = 422 +// Currently supported verson +const CURRENT_VERSION = 422 + + +const defaultOptions = { + // https://minecraft.gamepedia.com/Protocol_version#Bedrock_Edition_2 + version: CURRENT_VERSION +} + +module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION } \ No newline at end of file diff --git a/src/server.js b/src/server.js index 13fa23b..8223f4f 100644 --- a/src/server.js +++ b/src/server.js @@ -1,29 +1,12 @@ const Listener = require('@jsprismarine/raknet/listener') -const { ProtoDef, Parser, Serializer } = require('protodef') const { EventEmitter } = require('events') +const { createDeserializer, createSerializer } = require('./transforms/serializer') const { Encrypt } = require('./auth/encryption') const { decodeLoginJWT } = require('./auth/chains') const { Connection } = require('./connection') +const Options = require('./options') -var protocol = require('../data/newproto.json').types; - -function createProtocol() { - var proto = new ProtoDef(); - proto.addTypes(require('./datatypes/minecraft')); - proto.addTypes(protocol); - - return proto; -} - -function createSerializer() { - var proto = createProtocol() - return new Serializer(proto, 'mcpe_packet'); -} - -function createDeserializer() { - var proto = createProtocol() - return new Parser(proto, 'mcpe_packet'); -} +const log = (...args) => console.log(...args) class Player extends Connection { constructor(server, connection, options) { @@ -102,20 +85,10 @@ class Player extends Connection { } } -// Minimum supported version (< will be kicked) -const MIN_VERSION = 422 -// Currently supported verson -const CURRENT_VERSION = 422 - -const defaultServerOptions = { - // https://minecraft.gamepedia.com/Protocol_version#Bedrock_Edition_2 - version: CURRENT_VERSION, -} - class Server extends EventEmitter { constructor(options) { super() - this.options = { ...defaultServerOptions, options } + this.options = { ...Options.defaultOptions, options } this.serializer = createSerializer() this.deserializer = createDeserializer() this.clients = {} @@ -123,8 +96,8 @@ class Server extends EventEmitter { } validateOptions() { - if (this.options.version < defaultServerOptions.version) { - throw new Error(`Unsupported protocol version < ${defaultServerOptions.version}: ${this.options.version}`) + if (this.options.version < Options.MIN_VERSION) { + throw new Error(`Unsupported protocol version < ${Options.MIN_VERSION} : ${this.options.version}`) } } @@ -133,7 +106,7 @@ class Server extends EventEmitter { } onOpenConnection = (conn) => { - console.log('Got connection', conn) + log('new connection', conn) const player = new Player(this, conn) this.clients[this.getAddrHash(conn.address)] = player @@ -141,12 +114,12 @@ class Server extends EventEmitter { } onCloseConnection = (inetAddr, reason) => { - console.log('Close connection', inetAddr, reason) + log('close connection', inetAddr, reason) delete this.clients[this.getAddrHash(inetAddr)] } onEncapsulated = (encapsulated, inetAddr) => { - console.log('Encapsulated', encapsulated, inetAddr) + log(inetAddr.address, ': Encapsulated', encapsulated) const buffer = encapsulated.buffer const client = this.clients[this.getAddrHash(inetAddr)] if (!client) { @@ -158,7 +131,7 @@ class Server extends EventEmitter { async create(serverIp, port) { this.listener = new Listener(this) this.raknet = await this.listener.listen(serverIp, port) - console.log('Listening on', serverIp, port) + log('Listening on', serverIp, port) this.raknet.on('openConnection', this.onOpenConnection) this.raknet.on('closeConnection', this.onCloseConnection) diff --git a/src/serverTest.js b/src/serverTest.js index 7dccb65..a02ac35 100644 --- a/src/serverTest.js +++ b/src/serverTest.js @@ -42,21 +42,23 @@ server.on('connect', ({ client }) => { let ids = 0 for (var item of CreativeItems) { let creativeitem = { runtime_id: items.length } + const has_nbt = !!item.nbt_b64 if (item.id != 0) { - const hasNbt = !!item.nbt_b64 creativeitem.item = { network_id: item.id, auxiliary_value: item.damage || 0, - has_nbt: hasNbt|0, - nbt_version: 1, + has_nbt, + nbt: { + version: 1, + }, blocking_tick: 0, can_destroy: [], can_place_on: [] } - if (hasNbt) { + if (has_nbt) { let nbtBuf = Buffer.from(item.nbt_b64, 'base64') - let { result } = await NBT.parse(nbtBuf, 'little') - creativeitem.item.nbt = result + let { parsed } = await NBT.parse(nbtBuf, 'little') + creativeitem.item.nbt.nbt = parsed } } items.push(creativeitem) diff --git a/src/transforms/serializer.js b/src/transforms/serializer.js index 010e850..e07b63c 100644 --- a/src/transforms/serializer.js +++ b/src/transforms/serializer.js @@ -1,30 +1,52 @@ -'use strict'; -var ProtoDef = require('protodef').ProtoDef; -var Serializer = require('protodef').Serializer; -var Parser = require('protodef').Parser; - -var protocol = require('../../data/protocol.json').types; +const { ProtoDefCompiler, CompiledProtodef } = require('protodef').Compiler +const { FullPacketParser, Serializer } = require('protodef') function createProtocol() { - var proto = new ProtoDef(); - proto.addTypes(require('../datatypes/minecraft')); - proto.addTypes(protocol); + const protocol = require('../../data/newproto.json').types + console.log('Proto', protocol) + var compiler = new ProtoDefCompiler() + compiler.addTypesToCompile(protocol) + compiler.addTypes(require('../datatypes/compiler-minecraft')) + compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) - return proto; + const compiledProto = compiler.compileProtoDefSync() + return compiledProto +} + + +function getProtocol() { + const compiler = new ProtoDefCompiler() + compiler.addTypes(require('../datatypes/compiler-minecraft')) + compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) + + const compile = (compiler, file) => { + global.native = compiler.native // eslint-disable-line + const { PartialReadError } = require('protodef/src/utils') // eslint-disable-line + return require(file)() // eslint-disable-line + } + + return new CompiledProtodef( + compile(compiler.sizeOfCompiler, '../../data/size.js'), + compile(compiler.writeCompiler, '../../data/write.js'), + compile(compiler.readCompiler, '../../data/read.js') + // compiler.sizeOfCompiler.compile(fs.readFileSync(__dirname + '/../../data/size.js', 'utf-8')), + // compiler.writeCompiler.compile(fs.readFileSync(__dirname + '/../../data/write.js', 'utf-8')), + // compiler.readCompiler.compile(fs.readFileSync(__dirname + '/../../data/read.js', 'utf-8')) + ) } function createSerializer() { - var proto = createProtocol(); - return new Serializer(proto, 'packet'); + var proto = getProtocol() + return new Serializer(proto, 'mcpe_packet'); } function createDeserializer() { - var proto = createProtocol(); - return new Parser(proto, 'packet'); + var proto = getProtocol() + return new FullPacketParser(proto, 'mcpe_packet'); } module.exports = { createDeserializer: createDeserializer, createSerializer: createSerializer, createProtocol: createProtocol -}; +} \ No newline at end of file diff --git a/test/serialization.js b/test/serialization.js new file mode 100644 index 0000000..c7588ca --- /dev/null +++ b/test/serialization.js @@ -0,0 +1,119 @@ +const { createDeserializer, createSerializer } = require('../src/transforms/serializer') + +function test() { + const serializer = createSerializer() + const deserializer = createDeserializer() + + function write(name, params) { + const packet = serializer.createPacketBuffer({ name, params }) + console.log('Encoded', packet) + return packet + } + + function read(packet) { + const des = deserializer.parsePacketBuffer(packet) + return des + } + + async function creativeTest() { + let CreativeItems = require('../../data/creativeitems.json') + + let items = [] + let ids = 0 + for (var item of CreativeItems) { + let creativeitem = { runtime_id: items.length } + if (item.id != 0) { + const hasNbt = !!item.nbt_b64 + creativeitem.item = { + network_id: item.id, + auxiliary_value: item.damage || 0, + has_nbt: hasNbt, + nbt: { version: 1 }, + blocking_tick: 0, + can_destroy: [], + can_place_on: [] + } + if (hasNbt) { + let nbtBuf = Buffer.from(item.nbt_b64, 'base64') + let { result } = await NBT.parse(nbtBuf, 'little') + + const buf = NBT.writeUncompressed(result, 'littleVarint') + + console.log(nbtBuf, buf, JSON.stringify(result)) + + console.log('\n') + + let res2 = await NBT.parse(buf, 'littleVarint') + console.log(JSON.stringify(result), JSON.stringify(res2.result)) + console.assert(JSON.stringify(result) == JSON.stringify(res2.result), JSON.stringify(result), JSON.stringify(res2.result)) + + console.log('\n') + + creativeitem.item.nbt.nbt = result + } + } + + items.push(creativeitem) + console.log(JSON.stringify(creativeitem)) + // console.log(JSON.stringify(creativeitem)) + + var s = write('creative_content', { items: [creativeitem] }) + var d = read(s).data.params + + // console.log(JSON.stringify(d), JSON.stringify(s)) + // if (JSON.stringify(d) != JSON.stringify(creative_content)) throw 'mismatch' + } + } + + async function creativeTst() { + var creativeitem = { + "runtime_id": 1166, + "item": { + "network_id": 403, + "auxiliary_value": 0, + "has_nbt": true, + "nbt": { + "version": 1, + "nbt": { + "type": "compound", + "name": "", + "value": { + "ench": { + "type": "list", + "value": { + "type": "compound", + "value": [ + { + "id": { + "type": "short", + "value": 0 + }, + "lvl": { + "type": "short", + "value": 1 + } + } + ] + } + } + } + } + }, + "blocking_tick": 0, + "can_destroy": [], + "can_place_on": [] + } + } + + var s = write('creative_content', { items: [creativeitem] }) + var d = read(s).data.params + console.log(JSON.stringify(d)) + } + + + creativeTst() +} + +if (!module.parent) { + test() +} \ No newline at end of file