Compare commits

..

468 commits

Author SHA1 Message Date
rom1504bot
fa6acab0f3
Release 3.49.0 (#650)
* Release 3.49.0

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-10-01 21:21:34 -04:00
extremeheat
b48518a6e7
1.21.111 (#649) 2025-10-01 20:52:10 -04:00
thejfkvis
407756b938
Skin Data Changes (#647) 2025-10-01 20:51:53 -04:00
rom1504bot
0b9c49fedc
Release 3.48.1 (#638)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-10 02:31:46 -04:00
TSL534
6b1474d2c6
Update login client skinData (#635)
* Update for the Skin Data in the Login system

* Fix for CI

* CI Fix2

* Fixed comments again for CI

* Update login.js

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-08-10 01:47:18 -04:00
rom1504bot
6c659feb5d
Release 3.48.0 (#633)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-06 14:16:25 -04:00
extremeheat
06fb3de3a0
1.21.100 (#632)
* Update options.js

* Update README.md
2025-08-06 14:05:20 -04:00
rom1504bot
6f06a8996e
Release 3.47.0 (#625)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-04 16:48:07 -04:00
Tamás Papp
14daa2d95a
1.21.93 support (#623) 2025-07-04 16:47:12 -04:00
rom1504bot
80751d58a7
Release 3.46.0 (#618)
* Release 3.46.0

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-06-18 21:51:20 -04:00
extremeheat
c66cdd3d62
1.21.90 (#617)
* 1.21.90

* Update README.md
2025-06-18 21:31:21 -04:00
extremeheat
e503c47c79
Start work for 1.21.90 (#616)
* 1.21.90

* update

* remove 1.21.90 in ci
2025-06-18 20:14:38 -04:00
rom1504bot
29ba39343a
Release 3.45.0 (#603)
* Release 3.45.0

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-05-08 20:19:26 -04:00
extremeheat
e71fd513dd
1.21.80 (#602) 2025-05-08 19:51:20 -04:00
LunaryNet2
b6b0bcdd70
Update index.d.ts (#596) 2025-03-31 20:13:27 -04:00
rom1504bot
d88309507d
Release 3.44.0 (#595)
* Release 3.44.0

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-03-26 00:20:38 -04:00
extremeheat
065f41db8c
1.21.70 (#594)
* Update options.js

* Update README.md
2025-03-25 23:38:56 -04:00
rom1504bot
9f11f21991
Release 3.43.1 (#592)
* Release 3.43.1

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-03-19 14:03:47 -04:00
EntifiedOptics
47f342ca95
Fix server not correctly removing clients (#588)
* Properly remove connection from clients list

* Changed misname in onCloseConnection

* Update server.js fix

---------

Co-authored-by: JSbETms <137791538+JSbETms@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-03-18 17:01:49 -04:00
extremeheat
328785d8af
Quicken tests 2025-02-13 15:36:33 -05:00
rom1504bot
d4a9faf153
Release 3.43.0 (#577)
* Release 3.43.0

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-02-11 22:08:50 -05:00
Tamás Papp
eeb5e47e35
1.21.60 support (#570)
* Add support for ItemRegistryPacket (for shield item ID)

* simplify

* lint

* internal test

* update start_game handling

* test: capture events rejections

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-02-11 21:42:31 -05:00
Slauh!
2d7d32dfd1
Update API.md fix chat message handling (#575) 2025-02-06 13:54:38 -05:00
Max
0ed8e32be8
Fix version feature handling (#572)
* Replace unsupported version with next supported version

* add validity check

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
2025-01-31 14:02:04 -05:00
Romain Beaumont
75fa085e86
node 22 (#568) 2025-01-26 23:06:36 +01:00
dependabot[bot]
8e728255c7
Bump mocha from 10.8.2 to 11.0.1 (#548)
Bumps [mocha](https://github.com/mochajs/mocha) from 10.8.2 to 11.0.1.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.8.2...v11.0.1)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 23:06:04 +01:00
rom1504bot
cd9a31a435
Release 3.42.3 (#567) 2025-01-18 05:09:53 -05:00
extremeheat
38dc5a2561
Fix Server maxPlayers option (#565) 2025-01-18 02:51:08 -05:00
extremeheat
a79be99c0f
Update ci.yml 2025-01-02 18:53:50 -05:00
extremeheat
a37bc5054e
Add retry to tests 2025-01-02 16:16:33 -05:00
github-actions[bot]
e7bb990aea Release 3.42.2 (#558)
Update HISTORY.md
2024-12-15 13:22:17 +00:00
poekcosimo
dfcf5b2f1b Fix missing type serialization error (#557) 2024-12-15 13:21:21 +00:00
rom1504bot
aa3dafa5f4
Release 3.42.1 (#554)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-09 22:40:12 -05:00
extremeheat
24d3200181
Add 1.21.40 login fields (#553) 2024-12-09 17:56:03 -05:00
extremeheat
347e303ce4
Remove protodef varint types (#552)
Remove protodef types which are now natively in ProtoDef
2024-12-09 17:55:31 -05:00
rom1504bot
f48b50849a
Release 3.42.0 (#551)
* Release 3.42.0

* Update HISTORY.md

* Update README.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2024-12-05 01:08:37 -05:00
extremeheat
1c0836bff0
1.21.50 2024-12-05 00:44:03 -05:00
extremeheat
b4adf04845
Update vanilla test server setup (#550)
* Update test server

* lint
2024-12-05 00:05:30 -05:00
extremeheat
52358d7277
Update internal tests for 1.21.50 2024-12-04 23:13:16 -05:00
extremeheat
29c7b92d96
Update startVanillaServer.js 2024-12-04 22:47:15 -05:00
LucienHH
911e0e890f
Dynamic compression & batch header (#544) 2024-12-03 17:23:32 -05:00
rom1504bot
fa730786d5
Release 3.41.0 (#538)
* Release 3.41.0

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2024-10-24 23:00:42 -04:00
extremeheat
dd5c4de4f2
1.21.42 2024-10-24 22:46:20 -04:00
extremeheat
a4023b987b
Update options.js 2024-10-24 22:14:34 -04:00
extremeheat
cd937a45d0
Update startVanillaServer.js 2024-10-24 22:02:07 -04:00
rom1504bot
391ea9814c
Release 3.40.0 (#529)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-20 04:04:13 -04:00
poekcosimo
fc30c96135
1.21.30 support (#527) 2024-09-20 03:50:40 -04:00
extremeheat
cb530c8b45
Update tests (#528)
* Debug

* Update internal.js

* Update internal.js

* Update internal.js

* Update internal.js
2024-09-20 01:07:47 -04:00
rom1504bot
418efa1da6
Release 3.39.0 (#522) 2024-08-17 21:18:52 -04:00
extremeheat
3be55777fa
1.21.20 2024-08-17 20:52:54 -04:00
extremeheat
4c3f62567e
update disconnect packet 2024-08-17 20:03:02 -04:00
rom1504bot
a797201ecb
Release 3.38.0 (#514) 2024-07-11 00:16:58 -04:00
Tamás Papp
5d3986924d
Support 1.21.2, and add missing versions to type definitions (#510)
* Add missing versions to type definitions

* Add support for 1.21.2

* Add 1.21.2 to README
2024-07-10 20:58:47 -04:00
TheLegendaryTrashcan
c4593aa355
Fix example in README.md for 1.21 (#506)
* Fix example  in README.md for 1.21

* Update client.js

* Update README.md

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
2024-07-05 07:22:05 -04:00
w0ahL
84c5231b92
Don't send now deprecated tick sync packets on 1.21 and newer (#504)
* Tick Sync is now deprecated

* Add new function here

* Add check

* Remove useless require by me

* Fix
2024-06-16 16:51:22 -04:00
rom1504bot
1fee54f517
Release 3.37.0 (#505)
* Release 3.37.0

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2024-06-14 04:57:51 -04:00
extremeheat
5b2d78792c
Support 1.21.0 2024-06-14 04:43:57 -04:00
extremeheat
19ade45ea5
Update vanilla.js disable chunk tests
Should be done separately in p-chunk repo or in mineflayer
2024-06-13 23:07:17 -04:00
Kaaaaii
16e15d80a5
Fix typo in types (#501)
`ClientStatus.Disconected` is now `ClientStatus.Disconnected`
2024-05-31 10:20:59 -04:00
extremeheat
1ddc525169
Update README 2024-04-26 01:44:18 -04:00
rom1504bot
a4346f9638
Release 3.36.0 (#498)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-25 03:09:02 -04:00
extremeheat
bd32aa8d04
1.20.80 2024-04-25 02:47:16 -04:00
extremeheat
c65fea2916
helper-bot fix 2024-03-29 01:32:53 -04:00
extremeheat
3e5158bebe
helper-bot fix 2024-03-28 16:14:09 -04:00
extremeheat
a7ae528c16
update helper-bot for gh-helpers v0.2 2024-03-28 16:06:06 -04:00
rom1504bot
184339b9d4
Release 3.35.0 (#489)
* Release 3.35.0

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2024-03-13 23:32:19 -04:00
extremeheat
d8e707112a
1.20.71 2024-03-13 23:19:21 -04:00
extremeheat
5ee5744ec9
Update update helper for gh-helpers v0.1
Handle breaking changes to API
2024-03-09 15:46:17 -05:00
extremeheat
914ed6840b
Move github-helper script to new gh-helpers (#485) 2024-03-09 11:51:36 -05:00
extremeheat
ab93d0d082
Note npm update command in readme 2024-02-10 02:11:32 -05:00
rom1504bot
0132302e53
Release 3.34.0 (#481)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-07 21:05:28 -05:00
extremeheat
c278a03f95
1.20.61 support (#480)
* Update README.md

* Update options.js
2024-02-07 21:04:39 -05:00
extremeheat
d3161badc6
Compressor handling update for 1.20.60 (#479)
* support compressor in header

* use mcdata features

* cleanup
2024-02-07 20:03:26 -05:00
extremeheat
be6f0cde9f
Update and rename CONTRIBUTING.md to docs/CONTRIBUTING.md (#475) 2024-01-31 11:24:08 -05:00
GameParrot
842e66266f
Add flow and deviceType options to relay (#464)
* add flow and deviceType

for when authTitle is changed

* Update index.d.ts

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
2024-01-02 14:37:32 -05:00
rom1504bot
54840f818c
Release 3.33.1 (#472) 2023-12-27 13:06:30 -05:00
extremeheat
7b74cbf712
Fix zigzag type move in prismarine-nbt (#471)
* Fix rename of zigzag varint type in prismarine-nbt

* fix
2023-12-27 12:53:39 -05:00
rom1504bot
fd165b8908
Release 3.33.0 (#467)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-06 23:21:00 -05:00
extremeheat
d53211c6a1
1.20.50 (#466)
* Update options.js

* update readme
2023-12-06 22:41:23 -05:00
Tamas Papp
2ecf01d63e
Add 1.20.30 and 1.20.40 to index.d.ts (#461) 2023-10-26 22:41:43 -04:00
rom1504bot
d66dc1b237
Release 3.32.0 (#460) 2023-10-26 02:10:51 -04:00
extremeheat
63eb673c1f
1.20.40 (#459) 2023-10-26 00:53:20 -04:00
extremeheat
50911d2463
Update workflow node version 2023-10-24 15:16:35 -04:00
Spongecade
689658c4ab
Update Minecraft wiki link to new domain (#455) 2023-10-06 22:05:28 -04:00
rom1504bot
755ba31ea7
Release 3.31.0 (#450)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-09-20 02:40:49 -04:00
extremeheat
22502b90fd
1.20.30 2023-09-20 02:22:25 -04:00
extremeheat
f92db61c89
Add links field to server resource_packs_info 2023-09-20 00:47:37 -04:00
YeemiRouth
8f3b6c5aec
Update API.md (#448) 2023-09-18 13:56:42 -04:00
rom1504bot
4c7bb65ad0
Release 3.30.1 (#444)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-23 04:51:53 -04:00
GameParrot
f0f1351d40
Update Mojang public key used for logins (#443) 2023-08-23 04:43:02 -04:00
kotinash
2c00402a9e
index.d.ts: Fixed a typo (#441) 2023-08-09 20:26:55 -04:00
MrSterdy
50cd489f6e
Mark listen and close as async (#440) 2023-08-05 15:33:25 +02:00
MrSterdy
1414420574
Stop disconnecting when upstream packet deserialization fails (#435)
* Remove disconnect on upstream packet deserialization error

* Add disconnectOnParseError option

* Rename disconnectOnParseError option to omitParseErrors

* Update index.d.ts

* Update index.d.ts

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
2023-07-29 10:14:10 +02:00
Tamas Papp
010d57e78a
Add 1.20.0 and 1.20.10 to index.d.ts (#431) 2023-07-19 12:59:38 -04:00
rom1504bot
d063a6427a
Release 3.30.0 (#429)
* Release 3.30.0

* Update HISTORY.md

* Update HISTORY.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
2023-07-12 14:05:46 -04:00
extremeheat
46f11204ba
1.20.10 support (#428)
* 1.20.10 support

* Update README.md
2023-07-12 14:01:10 -04:00
extremeheat
fe3c3b01fc
Add commands workflow 2023-07-12 13:21:25 -04:00
GameParrot
b2c141c25f
Fix upstream relay batchingInterval (#425) 2023-07-04 07:27:40 -04:00
Nyrok
aa0b5743b5
Release 3.29.1 (#421) 2023-06-26 18:27:04 -04:00
extremeheat
d2c4430833
Add missing data to client login user chain (#420)
Fix connecting to pocketmine servers
2023-06-25 14:20:50 -04:00
extremeheat
d31b2b2aec
Add FAQ entry and replit warning on client ping error (#415) 2023-06-17 23:04:23 -04:00
Visual1mpact
0b7f3e037d
types: Fix Relay authTitle type (#418)
authType is a string. It was corrected in commit 8507286895 but this particular change was overlooked.

This solves the error:

node_modules/bedrock-protocol/index.d.ts:177:17 - error TS2304: Cannot find name 'title'.

177     authTitle?: title | string
                    ~~~~~


Found 1 error in node_modules/bedrock-protocol/index.d.ts:177
2023-06-13 09:42:31 -04:00
extremeheat
6d708339aa
Release 3.29.0 (#411)
* Release 3.29.0

* tests: swap UAs to see if fixes flaky CI

* lint

* tests: increase head timeout

* undo ua changes
2023-06-08 05:26:22 -04:00
extremeheat
97b8140fdd
1.20.0 support (#410) 2023-06-08 03:47:46 -04:00
Hitesh. V
79a6391709
Sponsoring (#402) 2023-05-20 15:04:32 +02:00
kinash
cd0ddc16e1
Update basicServer example to 1.19.80 (#399) 2023-05-20 15:00:52 +02:00
Hitesh. V
2cea7d6912
"JWT not active" FAQ (#397) 2023-05-09 20:07:06 -04:00
LucienHH
3b59446780
Release v3.28.1 (#396)
* Release v3.28.1

* Update HISTORY.md

Only include information relevant to package users

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
2023-05-04 15:23:03 -04:00
andriycraft
17d5b15bac
Update genPacketDumps.js (#393)
* Update genPacketDumps.js

* Update genPacketDumps.js
2023-05-04 12:59:56 -04:00
LucienHH
32e0c66997
Fix followPort option (#394)
* Fix `followPort` option

* Use correct default
2023-05-04 12:58:55 -04:00
extremeheat
8507286895
Update index.d.ts
inline versions list for helper-bot workflow
2023-04-30 19:21:11 -04:00
Hitesh
a8f1349c83
Update type definitions (#391)
* Better Types

* Fixed version

got that issue/information from U9G

Co-Authored-By: u9g <git@u9g.dev>

* removing undocumented type

---------

Co-authored-by: u9g <git@u9g.dev>
2023-04-30 19:19:04 -04:00
extremeheat
d96f1a1a69
Release 3.28.0 (#387) 2023-04-27 02:47:14 -04:00
extremeheat
d4226e0ffd
1.19.80 support (#386) 2023-04-27 02:34:03 -04:00
extremeheat
55cac72af1
Release 3.27.1 (#380) 2023-04-22 17:51:07 -04:00
extremeheat
3c23add812
Fix raknet backend selection (#379) 2023-04-22 17:47:39 -04:00
extremeheat
9cda78f9b5
Release 3.27.0 (#376) 2023-04-21 19:05:28 -04:00
Stephen O'Connor
92b5e42bb5
Update index.d.ts with optionals (#373) 2023-04-19 17:27:37 -04:00
andriycraft
e5b8cc8bde
Update code readability (#370) 2023-04-15 18:54:29 -04:00
Hitesh
27e85867a6
Expose ServerAdvertisement class (#368) 2023-04-14 00:24:14 -04:00
extremeheat
830a58144a
Update mc-data links 2023-04-05 18:28:15 -04:00
extremeheat
75c1447bb6
Release 3.26.0 (#358)
* Update package.json

* Update index.d.ts

* Update HISTORY.md
2023-03-15 01:11:49 -04:00
extremeheat
7a365aa764
1.19.70 support (#357)
* Update options.js

* Update README.md
2023-03-15 00:54:13 -04:00
Hitesh
9848398714
types: add some type hints (#354) 2023-03-15 00:38:28 -04:00
extremeheat
7a05b0a33e
Skip 1.19.30, 1.19.40 middle versions for faster CI
Only on github workflows
2023-03-02 06:14:58 -05:00
extremeheat
d631ed827f
Release 3.25.0 (#353) 2023-02-24 18:28:28 -05:00
Stephen O'Connor
3fd70f97c9
Support 1.19.63 (#352)
* README - bump version

* Update index.d.ts

* Update options.js

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
2023-02-24 18:07:04 -05:00
Hitesh
c1f008a3c9
Add close packet in server player API doc (#347) 2023-02-19 16:13:14 -05:00
extremeheat
ff658b2234
Update doc 2023-02-18 01:23:48 -05:00
extremeheat
1a37b4dac7
Release 3.24.0 (#346)
* Release 3.24.0

* skip 1.19.50
2023-02-17 17:24:12 -05:00
Tamas Papp
2c78372933
1.19.62 support (#345) 2023-02-17 16:44:27 -05:00
extremeheat
359a008b0c
Release 3.23.0 (#341) 2023-02-08 22:36:37 -05:00
extremeheat
fada3aa12a
1.19.60 support (#340) 2023-02-08 22:05:27 -05:00
extremeheat
5e395c5aa4
Add 1.19.51 to types 2023-02-04 14:42:07 -05:00
dependabot[bot]
beca68ee3e
Bump jsonwebtoken from 8.5.1 to 9.0.0 (#328)
Bumps [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) from 8.5.1 to 9.0.0.
- [Release notes](https://github.com/auth0/node-jsonwebtoken/releases)
- [Changelog](https://github.com/auth0/node-jsonwebtoken/blob/master/CHANGELOG.md)
- [Commits](https://github.com/auth0/node-jsonwebtoken/compare/v8.5.1...v9.0.0)

---
updated-dependencies:
- dependency-name: jsonwebtoken
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-21 23:32:42 +01:00
Jarco
d06386e0f3
added onMsaCode to ClientOptions (#335)
* added onMsaCode to ClientOptions

* Import the type instead of declaring it
2023-01-20 22:19:20 -05:00
Jarco
d7345ed732
add profilesFolder to ClientOptions (#331) 2022-12-29 18:07:02 +01:00
extremeheat
8c9e776a1a
Release 3.22.0 (#325) 2022-12-02 14:46:16 -05:00
William
74f2efa7a4
1.19.50 support (#324) 2022-12-02 14:34:28 -05:00
Black Hat
d66094a4fd
Add 1.19.41 support to readme (#322)
Co-authored-by: extremeheat <extreme@protonmail.ch>
2022-11-29 12:19:30 -05:00
extremeheat
6737ab09d0 startVanillaServer: handle server timeout on head request 2022-11-29 00:41:53 +00:00
extremeheat
b930840bec
Update README discord badge
Update README discord badge to match mineflayer
2022-11-01 17:38:41 -04:00
extremeheat
a326d42a10
Release 3.21.0 (#315)
* Update HISTORY.md

* Update package.json

* Update HISTORY.md

* Update startVanillaServer.js

* Update startVanillaServer.js

* Update startVanillaServer.js

* lint
2022-10-29 03:24:52 -04:00
extremeheat
e7f8357a36
Support 1.19.40 (#314) 2022-10-29 02:25:31 -04:00
Miner
58724960ba
types: Fix missing field in ServerAdvertisement (#313) 2022-10-27 17:41:31 -04:00
extremeheat
20da9446f0
Update microsoftAuth to set default flow option (#309)
* Update microsoftAuth to set default flow option

Explicitly specify to login through live.com

* Update package.json
2022-10-25 04:22:30 -04:00
extremeheat
616c587f14
Add 1.19.31 to types 2022-10-22 10:17:15 -04:00
extremeheat
c362968c5b
Release 3.20.1 (#300) 2022-10-15 17:43:55 -04:00
dependabot[bot]
9674fc0126
Bump mocha from 9.2.2 to 10.0.0 (#207)
Bumps [mocha](https://github.com/mochajs/mocha) from 9.2.2 to 10.0.0.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v9.2.2...v10.0.0)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-14 00:57:29 -04:00
dependabot[bot]
169e305c42
Bump raknet-node from 0.4.6 to 0.5.0 (#296) 2022-10-06 22:20:13 -04:00
Kurt Thiemann
9724ff41ed
Fix buffer length calculation in ServerAdvertisement (#292) 2022-10-04 02:57:00 -04:00
extremeheat
c51b55d4c7
Make CI less flaky (#293)
* add debug code to server start

* Reduce number of versions tested on Github Action CI to speed up tests

* test: Ask OS for free port instead of random int
2022-10-04 02:47:06 -04:00
extremeheat
3b51ebb6ee
Handle Relay serialization errors by kicking (#290)
* Handle Relay serialization errors by kicking

* lint
2022-09-26 22:52:34 -04:00
extremeheat
09a17c4ffb
CI: do not fail-fast 2022-09-26 20:48:48 -04:00
extremeheat
131733fb5a
Release 3.20.0 (#287)
* Release 3.20.0

* Update HISTORY.md
2022-09-24 17:49:52 -04:00
extremeheat
3295c0ddd9
Update doc (#288)
* Update API.md

* Update README.md

* Update API.md
2022-09-24 16:55:12 -04:00
extremeheat
7c716b9232
1.19.30 support (#286) 2022-09-24 16:47:03 -04:00
extremeheat
72dd749717
tools/vanillaServer: add HEAD request timeout
prevent long hangs
2022-09-24 15:46:33 -04:00
extremeheat
dbf15c4feb
use raknet-native for tests (#285) 2022-09-24 15:25:47 -04:00
extremeheat
f88c8d0bc4
1.19.30 support, improve error handling and server pong data (#284)
* Update server advertisement

* 1.19.30 protocol support
* Handle configurable compressor
* Support updated 1.19.30 login flow with NetworkSettings
* Improve serialization error handling on client

* refactor compressor handling

* Fix client on older versions, fix internal error handling

* Improve error handling

* Log console connection errors; use raknet-native for proxy test
2022-09-24 13:53:26 -04:00
extremeheat
30c583fcf3
Release 3.19.0 (#283)
* Update HISTORY.md

* Update package.json

* Remove viewer example

* Update HISTORY.md
2022-09-22 22:47:17 -04:00
extremeheat
99153c2de2
Add option for port redirection, fix Realm handling (#282)
* Port Redirect Fix - Only Redirect on Unspecified Port

* Update to use `followPort` option

Co-authored-by: Stephen O'Connor <oconnor39@gmail.com>
2022-09-22 15:14:49 -04:00
Stephen O'Connor
d3a085a260
Add Port Redirect Functionality (#278) 2022-09-14 22:31:31 -04:00
Stephen O'Connor
bf2de1dcf4
Add Get-AppxPackage command to FAQ.md (#276) 2022-09-11 23:19:32 -04:00
extremeheat
9d717744b6
Add 1.19.22 to types 2022-09-01 15:37:45 -04:00
extremeheat
ffe0fb42b5
Update index.d.ts 2022-08-25 14:05:14 -04:00
extremeheat
8913d48f42
Release 3.18.0 (#267)
* Update HISTORY.md

* Update package.json
2022-08-25 12:49:43 -04:00
extremeheat
7264cbe312
1.19.21 support (#266)
* Update options.js

* Update README.md
2022-08-25 12:06:57 -04:00
extremeheat
262a4eee00
Release 3.17.0 (#265) 2022-08-24 04:45:44 -04:00
extremeheat
f5c91ba1c9
Fix nbt encoding size on single null tag NBT (#264) 2022-08-24 00:50:01 -04:00
extremeheat
c395f0b05b
relay: Add multi-user login support (#258)
* relay: add multi-user login support

* relay: Fix close handling, connect username
2022-08-24 00:41:12 -04:00
Miniontoby
ce06762dce
test: Add -u flag unzipping vanilla server (#262)
* Fixed startVanillaServer.js

I fixed the CI by editing startVanillaServer.js and adding -u to unzip and tar

* Update startVanillaServer.js
2022-08-24 00:34:14 -04:00
CleSucre
3a0071f0ef
Add fields from 1.19.20 to login chain data (#259)
* Update Properties

* Ci Fix
2022-08-16 23:25:24 -04:00
extremeheat
e10c6133ec
Release 3.16.0 (#252)
* Update package.json

* Update HISTORY.md
2022-08-11 21:49:53 -04:00
extremeheat
0c77d8e252
1.19.20 support (#251)
* Update options.js

* Update README.md
2022-08-11 21:16:32 -04:00
extremeheat
d1ba619788
Fix proxy test waiting condition (#250)
* Fix proxy test waiting condition

* update test command

* Update proxy.js

* Update proxy.js

* Update proxy.js
2022-08-10 13:29:46 -04:00
b23r0
b36c55e112
Add new raknet library option (raknet-node) (#211)
* add new raknet library option (raknet-node)

* fix lint

* fix lint & add new options

* fix lint

* fix user option & add rust-raknet test.

* fix lint.

* add raknet backend option.

* remove useNativeRaknet option.

* add not found log.

* add test timeout size.

* fix js raknet return error.

* restore useNativeRaknet option.

* update doc.

* update options handling, back compat

* fix server doc

* Fix tests

* fix tests.

* fix lint.

* delay timeout.

* Update rak.js

* update raknet-node version.

* increase timeout.

* Update vanilla.js

* Update proxy.js

* Update internal.js

* update raknet-node version.

* update rust-raknet version.

* increase timeout test time

* increase timeout test time

* update backend version.

* change timeout

Co-authored-by: extremeheat <extreme@protonmail.ch>
2022-08-09 03:39:27 -04:00
extremeheat
d221da7613
Update version list for 1.19.11
Update index.d.ts
2022-07-26 21:05:51 -04:00
extremeheat
370ca96f2e
Update startVanillaServer.js
Debug log on CI
2022-07-21 06:24:15 -04:00
extremeheat
1542ab63d1
Release 3.15.0 (#236)
* Update options.js

* Update package.json

* Update README.md

* Update index.d.ts

* Update HISTORY.md
2022-07-18 22:03:01 -04:00
extremeheat
87a958e4ab Prereq update for 1.19.10 changes to server join flow 2022-07-18 16:33:58 -04:00
Jonathan Nagy
7263714562
Fix chat echo sample (#233)
* Fix chat echo sample

The `client.options.username` reference is optional for online connections.  Referencing it here when it is null will not filter out the echo text, causing an infinite loop.

* Update client.js
2022-06-27 21:49:54 -04:00
LucienHH
149b4fe182
Update auth.js (#231) 2022-06-24 18:48:33 -04:00
extremeheat
bdaf21b8ed
CI: increase timeout to 12min 2022-06-24 04:31:22 -04:00
extremeheat
19bc2519e7
types: add 1.19.2 to versions
Add 1.19.2 to versions
2022-06-22 14:57:33 -04:00
ATXLtheAxolotl
6e73a75138
Add Realm support to Relay (#226)
* add(): realm relay

* fix(): Non-async solution to realm proxy

* fix(): follow the guidlines of the strict linter

* fix(): Use Client constructor instead of createClient function

* types(): Update index.d.ts to include realms option in destination
2022-06-17 05:55:50 -04:00
extremeheat
c7b825820e
Release 3.14.0 (#225) 2022-06-09 16:06:38 -04:00
extremeheat
9fac48e948
1.19 support (#224) 2022-06-09 15:44:20 -04:00
extremeheat
b6d0ab3765
Make CI more reliable (#217)
* Use 2-attempt server start method in dump test

* Update startVanillaServer.js

replace download from curl to nodejs https

* fix syntax error

* corrections

* rm some logging

* fix
2022-06-09 14:46:05 -04:00
Stephen O'Connor
aacd4b4256
Better handle ping timeout, update documentation (#218)
* Better handle ping_timeout, update documentation

* Remove extra semicolon

* Change ping_timeout to client.emit('error', e) for more normalized handling

* Remove extra RakTimeout import
2022-06-09 13:45:58 -04:00
extremeheat
df31b5d63a
test/vanilla: temporary hack to allow 1.19 chunk tests to pass 2022-06-09 13:08:51 -04:00
Erik
2d7c210acc
Server: correct body.protocol_version to body.params.protocol_version (#221)
packet.data is
```json
{
  name: 'login',
  params: {
    protocol_version: 503,
    tokens: {
      identity: '...'
    }
  }
}
```

Therefore `body.protocol_version` outputs undefined.
2022-06-07 03:47:21 -04:00
extremeheat
f7c556ec94
Release 3.13.0 (#208) 2022-05-04 16:11:53 -04:00
extremeheat
f4bd49dca0
Add 1.18.31 to types 2022-04-30 19:25:33 -04:00
extremeheat
63d11c24bb
Update API documentation 2022-04-30 19:22:35 -04:00
ATXLtheAxolotl
fbe7ff79e8
Emit generic 'packet' event for server clients (#205)
* Send out an all event on ServerPlayer

For debugging purposes

* Add a space for linter

* Changed to be similar to Client's all

* A space :|

* Update serverPlayer.js
2022-04-30 19:18:00 -04:00
extremeheat
52156a0024
Add XUID field for client offline mode client chain (#203)
* Add XUID field for client offline mode client chain

* Update serverPlayer.js

handle alternative casing

* test/internal.js: randomize test port

* fix

* test server starting retry
2022-04-30 19:02:56 -04:00
extremeheat
f2d39a071b
Update index.d.ts 2022-04-21 22:28:38 -04:00
extremeheat
20c53c49d7
Release 3.12.0 (#199) 2022-04-21 14:04:10 -04:00
extremeheat
dc3fb5629e
1.18.30 (#198) 2022-04-20 16:11:02 -04:00
extremeheat
930d90ea30
Release 3.11.1 (#197)
* Update package.json

* Update HISTORY.md
2022-04-19 14:07:13 -04:00
dependabot[bot]
5f90777bd7
Bump minecraft-data from 2.221.0 to 3.0.0 (#195)
Bumps [minecraft-data](https://github.com/PrismarineJS/node-minecraft-data) from 2.221.0 to 3.0.0.
- [Release notes](https://github.com/PrismarineJS/node-minecraft-data/releases)
- [Changelog](https://github.com/PrismarineJS/node-minecraft-data/blob/master/doc/history.md)
- [Commits](https://github.com/PrismarineJS/node-minecraft-data/compare/2.221.0...3.0.0)

---
updated-dependencies:
- dependency-name: minecraft-data
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-19 14:03:54 -04:00
extremeheat
9a4c7f03ea
Release 3.11.0 (#194) 2022-04-09 20:18:11 -04:00
LucienHH
cde600d51e
Implement Realm joining (#193)
* Pass client options to ping

* Implement RealmAPI to auth

* Add Realm join example

* Update package.json

* Update README.md

* Update index.d.ts

* Show one option, remove listener

* Fix wording

* Explain options

* Optional fields

* Fix typo

* Moved retry ad host/port extraction to prealms

* Add docs

* Fix lint

* Depend on prealms release

Co-authored-by: LucienHH <Lucien.holloway@aprox.co.uk>
2022-04-09 13:11:12 -04:00
extremeheat
dfff13867d
Refactor client connection sequence (#189)
* Refactor client connection sequence
Allow connection info to come in after Client construction, emit "connect_allowed" similar to nmp

* Fix breaking ping behavior change

* fix createClient connect callback

* correct behavior

* remove comments

* refactor impl

* fix incorrect use of `this`
2022-03-27 22:15:20 +02:00
CreeperG16
0dc586db9c
Add profilesFolder to Relay (#192) 2022-03-26 14:31:54 -04:00
circuit10
72c07bb7a0
Emit error from relay when server can't be pinged (#191) 2022-03-25 22:25:55 -04:00
circuit10
92785a1167
Pass relay onMsaCode to client (#190) 2022-03-25 12:21:47 -04:00
extremeheat
550b1a19b4
Mark raknet-native as required dependency (#188)
JS raknet implementation needs some fixes, don't allow silent fail of this dep
2022-03-20 22:51:17 +01:00
extremeheat
817fd918ce
Ignore unconnected packets, remove babel (#185)
* Ignore unconnected packets, remove babel

* re-enable proxy test on ubuntu runner
2022-02-21 10:35:24 +01:00
extremeheat
330c819e7f
Release 3.10.0 (#180)
* Update HISTORY.md

* Update package.json

* Update README.md
2022-02-09 19:07:50 +01:00
extremeheat
957c83995a
1.18.11 (#179)
* Fix js raknet client disconnection

* 1.18.11
2022-02-09 12:15:35 -05:00
extremeheat
14af5fe04f
Switch to sync zlib with 512k chunks, adjustable compression level (#174)
* Switch to sync zlib with 512k chunks, adjustable compression level

* update serverPlayer
2022-02-04 20:30:21 -05:00
extremeheat
2aa3b9826a
Release 3.9.0 (#173) 2022-01-08 13:40:22 -05:00
extremeheat
eb5ebc6650
Bump bedrock-provider in tests (#172) 2022-01-07 18:36:38 -05:00
extremeheat
058c280b66 fix relay connection close issue 2022-01-03 11:11:07 -05:00
extremeheat
abc1444929 Add missing login field 2022-01-03 11:11:07 -05:00
extremeheat
a1698d712f relay: fix empty chunk loading issues, make chunk caching optional 2022-01-03 11:11:07 -05:00
extremeheat
49fd2b69ee client: make console connection logging optional 2022-01-03 11:11:07 -05:00
extremeheat
7acacc1a13
Update docs (#165)
* Update API.md

* Update CONTRIBUTING.md
2021-12-26 15:53:49 +01:00
extremeheat
bb2179a8cb
Fix port collision caused by race condition in tests (#164)
* Fix port collision caused by race condition in tests

The ipv6 port is not randomized, if the previous ports for the last test didn't get freed in time, the server can crash on launch

* try again

* Update index.d.ts

* Update genPacketDumps.js
2021-12-15 23:55:32 -05:00
extremeheat
e3e553bf91
index.d.ts: update version enum 2021-12-07 23:23:53 -05:00
extremeheat
572b83047c Release 3.8.0 2021-12-04 10:48:14 -05:00
extremeheat
1a1fa618e4
1.18 update (#157) 2021-12-04 01:10:47 -05:00
extremeheat
b0856e0cc7
update docs and error handling (#155) 2021-11-28 00:57:41 -05:00
extremeheat
1b422ac4ce
Update prismarine-auth usage (#153) 2021-11-12 22:14:15 +01:00
Romain Beaumont
214c34c44b
Release 3.7.0 2021-11-07 12:55:11 +01:00
dependabot[bot]
389a68b98f
Bump prismarine-nbt from 1.6.0 to 2.0.0 (#148)
Bumps [prismarine-nbt](https://github.com/prismarinejs/prismarine-nbt) from 1.6.0 to 2.0.0.
- [Release notes](https://github.com/prismarinejs/prismarine-nbt/releases)
- [Changelog](https://github.com/PrismarineJS/prismarine-nbt/blob/master/HISTORY.md)
- [Commits](https://github.com/prismarinejs/prismarine-nbt/compare/1.6.0...2.0.0)

---
updated-dependencies:
- dependency-name: prismarine-nbt
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-07 12:48:01 +01:00
extremeheat
75bf22d619
1.17.40 update (#150)
* 1.17.40

* Update mcdata to 2.96.0

Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
2021-11-07 12:47:45 +01:00
extremeheat
8569b9823f
Release 3.6.1 2021-10-08 16:13:34 -04:00
extremeheat
adfa248e2d
Default createClient to latest version, fix server motd version (#144)
* Default createClient to latest version, fix server motd version

* Update vanilla.js
2021-10-08 05:56:10 -04:00
迷渡
ac2e9852be
fix spawn event (#139) 2021-10-04 19:20:12 -04:00
extremeheat
10feeea4ca
Release 3.6.0 (#141) 2021-10-04 13:27:38 -04:00
dependabot[bot]
72a4743993
Bump mocha from 8.4.0 to 9.1.2 (#138)
Bumps [mocha](https://github.com/mochajs/mocha) from 8.4.0 to 9.1.2.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v8.4.0...v9.1.2)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 21:22:07 +02:00
extremeheat
f8ea6c01f4
Use minecraft-data for protocol data (#126)
* use minecraft-data protocol data

* use minecraft-data for extra data

* Update .npmignore

* update skin data

* fix example

* remove .gitattr

* fix mcdata skin import, disable install script
2021-09-25 22:57:29 +02:00
extremeheat
1f7e94e5db
Update some examples, cleanup (#136)
* Update some examples, cleanup

* lint
2021-09-18 19:50:43 -04:00
Romain Beaumont
2056b7e001
add protocol doc link 2021-08-24 05:28:55 +02:00
Jordan Jones
c5858d5add
Implement Prismarine Auth (#131)
* add prismarine-auth and mfp

* implement prismarine-auth

* putting this on the record, i am stupid

* Remove unneeded files

* Use export from prismarine-auth

* fix cache path

* default the authTitle to MNS

needs testings

* Remove unnecessary comment
2021-08-16 13:06:19 +02:00
u9g
3267bbfca1
Fix typo in example (#130) 2021-08-12 00:05:29 -04:00
u9g
2d42caafb9
Fix Typo in readme example (#129)
* Fix Typo in readme example

* ServerAdvertisement: Fix motd name doc/code inconsistency

* Update advertisement.js

* Update advertisement.js

Co-authored-by: extremeheat <extreme@protonmail.ch>
2021-08-10 21:17:41 -04:00
u9g
2f941335f6
Fix small typo in helper-bot (#128) 2021-08-10 02:36:26 -04:00
extremeheat
72850b7f60
helper-bot: handle condition where there are no 3rd party commits
The helper-bot will still automatically update the PR if there are new commits while PR is still open
2021-08-10 01:17:39 -04:00
extremeheat
b3edee0b4a
helper-bot: fix url escaping 2021-08-09 23:19:08 -04:00
extremeheat
37244db6dc
Run tests on windows, disable proxy test on ubuntu CI runner (#125) 2021-08-09 23:16:17 -04:00
extremeheat
8845621e41
helper-bot: fix API call caching issue
Fix issue with server caching the results
2021-08-09 18:24:41 -04:00
extremeheat
254dbefcd4
Add update helper script (#117)
* Squashed commit from 'helper' branch

* CI: run "update-helper" script on cron

* Update index.js

* disable proxy test

* lint

* re-add proxy test with delay

* lint

* Fix cron time
2021-08-07 23:41:01 +02:00
extremeheat
a1fe4802e6 Release 3.5.1 2021-08-02 05:32:43 -04:00
extremeheat
0437e83fe9
fix 1.17.10 npc packet serialization (#119) 2021-08-02 00:34:13 -04:00
u9g
3b96765f45
Update link to proxy / mitm doc (#118) 2021-08-01 05:21:34 -04:00
extremeheat
0ab783e1e6
Release 3.5.0 2021-07-17 15:30:17 -04:00
extremeheat
f530677245
Option to use JS implementation of RakNet, fix 1.17.10 issue (#110)
* Default to JS implementation of RakNet

* update server also, adjust test timeout based on number of versions

* 1.17.10: fix texture pack issue, keep raknet default at C++

* force_build
2021-07-17 05:30:13 -04:00
extremeheat
987bf43987
1.17.10 protocol support (#109)
* 1.17.10 support

* add protocol.json

* add extra particle IDs
2021-07-14 17:30:10 -04:00
extremeheat
1cdb0e4c55 Update protodef version 2021-06-23 00:10:58 -04:00
extremeheat
b546cda899 Logging improvements 2021-06-23 00:10:58 -04:00
extremeheat
3f5c4ad12e Relay proxy fixes, handle disconnect 2021-06-23 00:10:58 -04:00
extremeheat
565ae4583d Protocol updates to sync with minecraft-data 2021-06-23 00:10:58 -04:00
JammSpread
90d12fa696
fix: type definition errors found related to ServerAdvertisements. (#103)
* fix: online/max player count type definition.

Fixes the type definition of the online/max players to be an immediate
child of the ServerAdvertisement class so that it can be accessed
properly on TypeScript.

Signed-off-by: JK <u.sslooky@gmail.com>

* feat: implement advertisementFn type definition.

Adds the type definition for the 'advertisementFn' server option.

Signed-off-by: JK <u.sslooky@gmail.com>

* fix: minor advertisementFn typo in API docs.

Fixes a minor typo found in the API docs that misspells
'advertisementFn' as 'advertismentFn'.

Signed-off-by: JK <u.sslooky@gmail.com>

* feat: add skipPing to the API docs.

Adds the 'skipPing' client option to the API docs.

Signed-off-by: JK <u.sslooky@gmail.com>

* feat: add connectTimeout type definition.

Adds the 'connectTimeout' client option to the package type definitions.

Signed-off-by: JK <u.sslooky@gmail.com>
2021-06-11 18:45:54 -04:00
extremeheat
6a03c9813c Release 3.4.0 2021-06-09 17:53:16 -04:00
extremeheat
5f0adfab76
1.17 support (#99) 2021-06-09 17:26:44 -04:00
u9g
d02eb6c041
update connect version based on ping response & fix typings (#101)
* update connect ver based on ping resp& add typings

* Fixed and added extremeheat suggestion

* Update API.md

* Update createClient.js
2021-06-09 13:40:45 -04:00
JammSpread
e16e8381e5
fix: ping types. (#100)
* fix: ping types.

Sets the TypeScript definition file to include more accurate types for
the ping function.

Signed-off-by: JK <u.sslooky@gmail.com>

* fix: changed usage.

Reverts the usage to the previous method of entering in ping options.
This makes it so that the host and port can once again be entered by
their order alone.

Signed-off-by: JK <u.sslooky@gmail.com>
2021-06-08 22:57:55 -04:00
extremeheat
9aba6b436d
Release 3.3.0 (#96)
* Update package.json

* Update HISTORY.md

* Update package.json
2021-06-05 06:40:55 -04:00
extremeheat
9113b0967e
Merge pull request #95 from extremeheat/protofix3
Protocol updates for 1.16
2021-06-05 02:05:50 -04:00
extremeheat
700ee08e11 correct type definitions 2021-06-04 19:52:09 -04:00
extremeheat
187e74b823 use protodef-compiler-fix to fix npm install 2021-06-04 19:51:22 -04:00
dependabot[bot]
69b7c5c9f4 Bump leveldb-zlib from 0.0.26 to 1.0.1
Bumps [leveldb-zlib](https://github.com/extremeheat/node-leveldb-zlib) from 0.0.26 to 1.0.1.
- [Release notes](https://github.com/extremeheat/node-leveldb-zlib/releases)
- [Changelog](https://github.com/extremeheat/node-leveldb-zlib/blob/master/HISTORY.md)
- [Commits](https://github.com/extremeheat/node-leveldb-zlib/compare/v0.0.26...1.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-04 16:59:18 -04:00
extremeheat
ab70115bd8 Protocol updates for 1.16
* Login packet uses new Encapsulated type
* Add missing packet schemas
* Add more docs
Breaking: revises some field names
2021-06-03 16:52:19 -04:00
extremeheat
6da8a9b2fc Minor protocol fix for entity data 2021-06-03 16:52:08 -04:00
extremeheat
0f0d064b13 Revert "temporary install fix"
This reverts commit 01575ecd6f.
2021-06-03 04:38:49 +00:00
extremeheat
01575ecd6f temporary install fix 2021-06-03 04:25:47 +00:00
extremeheat
d981515a75 Release 3.2.1 2021-06-01 20:10:59 -04:00
extremeheat
e9098aa744 test/proxy: extra debug logging 2021-06-01 20:10:59 -04:00
extremeheat
5af828bcb7 Update types, add authTitle option to relay, client renderDistance -> viewDistance 2021-06-01 20:10:59 -04:00
extremeheat
6a7ad6ab98 protocol: Fix position serialization 2021-05-27 12:38:46 -04:00
extremeheat
025c2cfac6 Release 3.2.0 2021-05-25 22:01:03 -04:00
extremeheat
9cb4a888f4
Fix empty chunks on proxy spawn (#89)
* Fix empty chunks on proxy spawn

Hack to fix issue with chunks not loading when client joins the proxy
* Queue chunk, respawn packets and only send them after player spawn

* fix lint
2021-05-24 12:07:03 -04:00
extremeheat
f0fbf4f859
Send skin data, protocol updates (#88)
* Add skin data

* Serialization updates
* Dynamic shield item id
* NBT reading/writing on void type uses 0 length, fix some third party servers

* Fix proxy empty chunk issue

* Fix scoreboards
compiler needs ../

* fix indentation

* Fix set_score packet

* Fix readme title auth doc

* Implement new compiler vars
2021-05-24 10:17:09 -04:00
extremeheat
76febb29f1
Support xbox title + live.com auth (#86)
* preliminary support for xbox title + live.com auth

* cleanup

* export title list

* add to api docs

* Verify that minecraft token has titleId if did titleAuth

* Minor changes
2021-05-19 09:53:55 -04:00
extremeheat
f644595b4b
Merge pull request #85 from extremeheat/tpf 2021-05-15 00:04:16 -04:00
extremeheat
879a4c21ba Rename src/auth -> src/handshake 2021-05-12 04:29:34 -04:00
extremeheat
7d532e8c7e protocol updates 2021-05-11 21:40:53 -04:00
extremeheat
b8f6ab4ed3 Remove crypto deps
Use node crypto lib
2021-05-11 21:17:31 -04:00
extremeheat
3a4335a2ae
Fix third party servers, optional client encryption (#83)
* Fix CBMC connections, fix js-raknet
* Remove electron support

* make raknet-native an optional dep

* protocol: fix skin serialization

* enable debugging output in tests

* Fix tests

* Allow server to skip encryption

* update protocol docs
2021-05-11 13:39:46 -04:00
Nihat
bb88115e5e
Update readme (#81) 2021-05-06 13:23:45 -04:00
extremeheat
8de044aafa update package.json 2021-05-05 12:20:27 +02:00
extremeheat
a8188c07ef Release 3.1.0 2021-05-05 12:20:27 +02:00
extremeheat
ab8b6ddc0c
Protocol updates, maps and rotation (#77)
* protocol updates
* Add clientbound map packet impl
* Add missing byte rotation implementation

* github: auto collapse generated diffs

* protocol doc fix

* protocol collision fix

* Fix map pixels
2021-05-04 13:56:12 -04:00
extremeheat
c9c162bc16 Increase test timeouts 2021-05-04 03:00:08 -04:00
extremeheat
faf7373dfd Fix microsoft auth refresh 2021-05-04 03:00:08 -04:00
dependabot-preview[bot]
cbfd9eac1d
Upgrade to GitHub-native Dependabot (#75)
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-05-02 01:04:32 +02:00
extremeheat
cf006ab12d
Fix readme install command 2021-04-25 09:28:15 -04:00
u9g
39659cf48b
rename hostname to host (#74) 2021-04-23 03:23:43 -04:00
extremeheat
7c6439b301
Add version check (#73)
* Add version check

* fix server import
2021-04-21 07:23:06 -04:00
extremeheat
b60fd53ad5
Add relay proxy to tests, docs, fix offline (#71)
* Add relay proxy to tests, docs

* Add proxy example, type defs

* update docs

* proxy: forward login packet, fix offline
2021-04-21 06:22:51 -04:00
extremeheat
5ea8056e04 Fix relay, cleanup 2021-04-20 09:55:57 -04:00
extremeheat
1538c3fc03 Add server MOTD options 2021-04-20 09:55:57 -04:00
extremeheat
7263791581 Async batching 2021-04-20 09:55:57 -04:00
extremeheat
999bfd3569
Enforce server auth, ping on createClient, update docs (#68)
* Add createServer, ping on createClient, update README

* fix createClient keepalive

* resort readme, fix node 14

* Enforce auth on server connections, fix close/connect issues

* add type definitions, update readme, docs

* Wait some time before closing connection, update docs

* wait for server close in tests, fix race bug

* export a ping api

* Rename api.md to API.md

* add ping example
2021-04-18 09:19:59 -04:00
extremeheat
d3723ef42a
1.16.220 support (#66)
* 1.16.220 initial support

* 1.16.220 fixes, electron gcm

* 1.16.220 item stack fix
2021-04-16 16:40:38 -04:00
u9g
d8ff48258c
Add createClient (#61)
* Initial commit

* remove comment

* export obj

* fix export

* add to api.md

* fix old refs to nmp
2021-04-14 23:39:05 +02:00
Romain Beaumont
0bf6b8bded
Merge pull request #60 from extremeheat/examples
Add viewer, protocol and crypto updates
2021-04-14 23:36:52 +02:00
extremeheat
8663247a2a Lessen logging, fix JWT param 2021-04-14 07:42:36 -04:00
extremeheat
aec81efba8 Merge branch 'master' of https://github.com/PrismarineJS/bedrock-protocol into examples 2021-04-14 07:25:38 -04:00
extremeheat
fc39d69798 protocol: inventory, recipe updates 2021-04-14 07:23:42 -04:00
u9g
21a0dcd393
Add metadata dumping to dumpPackets.js (#63) 2021-04-14 04:23:04 -04:00
u9g
5499f956b9
Refactor data-provider use in server ex (#64) 2021-04-14 04:19:45 -04:00
extremeheat
880b052093
Add CONTRIBUTING.md (#59)
* Create CONTRIBUTING.md

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md
2021-04-12 00:47:38 +02:00
u9g
ed2dbcc568 Add packet dumper with same format as java dumper 2021-04-11 00:56:33 -04:00
extremeheat
a9ad14008e viewer: add sprint, sneaking 2021-04-08 01:32:20 -04:00
extremeheat
41b9f7b383 viewer: working first person 2021-04-07 07:15:22 -04:00
extremeheat
2aade94033 update raknet-native, remove aes-js 2021-04-07 06:59:22 -04:00
extremeheat
01f19164ba viewer: key control movement 2021-04-07 01:56:32 -04:00
extremeheat
7a830317ea viewer: first person views 2021-04-04 03:52:06 -04:00
extremeheat
f4c3d900f1 Merge branch 'new' into examples 2021-04-03 23:12:29 -04:00
extremeheat
32d52e9878 viewer: refactor and cleanup 2021-04-03 22:56:36 -04:00
extremeheat
d2c0d3c386 viewer: add proxy example 2021-04-03 20:34:42 -04:00
extremeheat
88f88559af switch to browserify-crypto on electron 2021-04-03 20:30:01 -04:00
extremeheat
b79e4a65e4 packet fixes, login chain update, raknet updates 2021-04-03 20:25:53 -04:00
extremeheat
f5321dd277 create pviewer exxample 2021-03-30 01:39:28 -04:00
extremeheat
a55eaddc98
Add packet dumper, new server example, internal client/server test (#4)
* Add packet dumper, configuable vanilla server, client events

* Fix server/client closing

* Add internal server test

* protocol: use WindowID types

* Add internal client/server test

* test timeout fixes

* client example updates

* update server example, use protocol updates
Server example with bedrock-provider
Use 64-bit varints for entity runtime ids

* fix internal test packet path
2021-03-26 05:19:08 -04:00
extremeheat
47c76ee597 fix internal test packet path 2021-03-26 05:13:01 -04:00
extremeheat
3369fc2790 update server example, use protocol updates
Server example with bedrock-provider
Use 64-bit varints for entity runtime ids
2021-03-26 04:48:23 -04:00
extremeheat
0bdd071876 client example updates 2021-03-26 04:48:21 -04:00
extremeheat
be98fc6cf8 test timeout fixes 2021-03-26 04:48:20 -04:00
extremeheat
43ef9c9430 Add internal client/server test 2021-03-26 04:48:18 -04:00
extremeheat
3f5b82f0f4 protocol: use WindowID types 2021-03-26 04:48:17 -04:00
extremeheat
bb9b94fa02 Add internal server test 2021-03-26 04:48:15 -04:00
extremeheat
e22dfea599 Fix server/client closing 2021-03-26 04:48:14 -04:00
extremeheat
cf6471f6eb Add packet dumper, configuable vanilla server, client events 2021-03-26 04:48:13 -04:00
extremeheat
184de537f5 Remove old tests, lint tests 2021-03-23 03:28:50 -04:00
extremeheat
b079ebc676 Add server close 2021-03-23 03:28:50 -04:00
extremeheat
edcc370426
Add index, .npmignore, connect timeout (#1)
* add index and .npm ignore

* add connect timeout

* update protocol.json

* Update ci.yml

* Update ci.yml
2021-03-22 15:07:25 -04:00
extremeheat
6803d742e6 player input, transaction, bitflag updates 2021-03-22 04:21:06 -04:00
extremeheat
b569cb6353 update build script 2021-03-21 19:41:52 -04:00
extremeheat
597453100f serializer: absolute path imports 2021-03-21 16:16:20 -04:00
extremeheat
514a84fd19 1.16.210 fix 2021-03-21 16:03:24 -04:00
extremeheat
6b7138695e 1.16.210 update 2021-03-21 07:54:04 -04:00
Romain Beaumont
37f7d3de99 fix badges 2021-03-20 23:15:35 +00:00
Romain Beaumont
9c36b71bf6 finish renaming to bedrock-protocol 2021-03-20 23:13:14 +00:00
Romain Beaumont
e6cc00ef16
add npm install in publish.yml 2021-03-21 00:01:21 +01:00
extremeheat
458136d877
Vanilla server tests, client offline mode (#49)
* vanilla server launcher

* update package.json

* re-add babel to fix standard

* fix ci

* add buffer-equal

* simple fixes

* add offline client support

* fix closing bugs, proper wait for server start

* add test to mocha

* change test timeout to 2 min

* increase timeouts

Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
2021-03-17 23:04:14 +01:00
extremeheat
bd97a8e1b7
client close handling, add spawn event (#48) 2021-03-14 00:49:54 +01:00
Romain Beaumont
99cce3b371
setup some CI and basics (#47) 2021-03-14 00:49:17 +01:00
extremeheat
58e011e06d
Login refactoring (#45)
* Refactor auth + encryption

Fix a bug with client account token caching

* move some files
2021-03-13 19:50:16 +01:00
dependabot-preview[bot]
ff787ba8ad
Bump uuid-1345 from 0.99.7 to 1.0.2 (#41)
Bumps [uuid-1345](https://github.com/scravy/uuid-1345) from 0.99.7 to 1.0.2.
- [Release notes](https://github.com/scravy/uuid-1345/releases)
- [Commits](https://github.com/scravy/uuid-1345/compare/0.99.7...1.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-03-13 19:46:40 +01:00
dependabot-preview[bot]
66ba0c8a5c
Bump mocha from 2.5.3 to 8.3.2 (#46)
Bumps [mocha](https://github.com/mochajs/mocha) from 2.5.3 to 8.3.2.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v2.5.3...v8.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-03-13 19:46:25 +01:00
extremeheat
1c582acdb5
Standard (#44)
* standard

* remove old examples
2021-03-13 19:38:31 +01:00
extremeheat
df8612e355
Start work on multi-version support, test cleanup (#43)
* some cleanup

* start work on multi-version support

* undelete some old examples, can update them later

* move old examples
2021-03-12 20:20:25 +01:00
Romain Beaumont
3ffcf841ea
Update README.md 2021-03-11 13:30:44 +01:00
Romain Beaumont
2905d39dd4 remove yarn file 2021-03-11 12:11:42 +00:00
Romain Beaumont
8a6158bc2d
Merge pull request #33 from extremeheat/new
1.16
2021-03-11 13:02:48 +01:00
extremeheat
667030370e varint64 impl, inventory and other packet fixes 2021-03-11 06:05:04 -05:00
extremeheat
9e1500ec77 add new packets and working proxy 2021-03-09 02:13:47 -05:00
extremeheat
86dcbc1f49 use raknet-native with js fallback 2021-03-08 02:36:25 -05:00
extremeheat
f00357eca6 Logging updates 2021-03-05 02:13:40 -05:00
extremeheat
863d51c56a Protocol updates, new packets 2021-02-25 22:18:25 -05:00
extremeheat
b64a22a8fc protocol updates, movement fixes
packet_map is now auto-generated
2021-02-25 17:47:14 -05:00
extremeheat
c7823ab71a cleanup
Update packages, remove  junk
2021-02-25 15:21:56 -05:00
extremeheat
9bd8f4c2bb server sending generated chunks 2021-02-25 13:11:22 -05:00
extremeheat
93ab3064ab update raknet path 2021-02-21 15:36:36 -05:00
extremeheat
4035295cdd packet batching + working client/server spawning 2021-02-21 15:26:34 -05:00
extremeheat
f3604fa9b5 protocol updates
Add some updates
2021-02-21 15:22:04 -05:00
extremeheat
57290cd180 client packet verification 2021-02-20 01:14:59 -05:00
extremeheat
8c40910809 protocol updates 2021-02-20 01:08:38 -05:00
extremeheat
0ed5a8d061 protocol updates, packet dumping 2021-02-18 13:33:30 -05:00
extremeheat
8db7305eed move raknet to worker 2021-02-18 01:50:46 -05:00
extremeheat
fe05a3d645 fix decryption 2021-02-18 01:41:00 -05:00
extremeheat
4e3c5f8e31 implement command packet 2021-02-17 01:13:47 -05:00
extremeheat
b6f8fff01e disable some logging 2021-02-16 23:31:19 -05:00
extremeheat
7b00432c7b protocol updates 2021-02-16 03:24:25 -05:00
extremeheat
38840e2c15 Merge branch 'master' into new 2021-02-13 21:07:08 -05:00
extremeheat
b2a8d6fa2d rebasing 2021-02-13 21:04:22 -05:00
extremeheat
233f0d33a6 workingish client connection to server
Joining a vanilla server is still broken (related to encryption), but minet connects
2021-02-13 18:42:43 -05:00
extremeheat
49cff40097 cliet auth 2021-02-10 05:04:10 -05:00
extremeheat
60e57fad44 protocol updates, use ProtoDef compiler 2021-02-10 01:32:54 -05:00
extremeheat
1a7205b3d1 update protocol, add some doc 2021-02-07 19:50:15 -05:00
extremeheat
51214fcf29 seperate packet map from proto.yml 2021-02-07 16:47:07 -05:00
extremeheat
da85b80c59 server player extends connection 2021-02-07 05:28:03 -05:00
extremeheat
73d4dd1325 protocol def updates 2021-02-07 05:25:22 -05:00
extremeheat
9c171aa0da seperate server/serverTest 2021-02-03 13:26:13 -05:00
extremeheat
51c1942153 move files 2021-02-03 13:17:13 -05:00
extremeheat
d92a38d3cd add creativeitems, biome definitions 2021-02-03 13:01:51 -05:00
extremeheat
040887e9df fix zlib decryption issue 2021-01-30 02:36:34 -05:00
extremeheat
d578eae98f add encryptor 2021-01-30 01:40:34 -05:00
extremeheat
902a57c722 working server 2021-01-29 00:50:26 -05:00
Romain Beaumont
fef514b84d
Merge pull request #30 from PrismarineJS/snyk-fix-c02861987312d03da9de54bf70a04610
[Snyk] Security upgrade uuid-1345 from 0.99.7 to 1.0.1
2020-05-17 12:58:54 +02:00
Romain Beaumont
db08f34619
Merge pull request #31 from PrismarineJS/dependabot/npm_and_yarn/uuid-1345-1.0.1
Bump uuid-1345 from 0.99.7 to 1.0.1
2020-05-17 12:10:41 +02:00
dependabot-preview[bot]
93b4da32da
Bump uuid-1345 from 0.99.7 to 1.0.1
Bumps [uuid-1345](https://github.com/scravy/uuid-1345) from 0.99.7 to 1.0.1.
- [Release notes](https://github.com/scravy/uuid-1345/releases)
- [Commits](https://github.com/scravy/uuid-1345/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-17 09:56:09 +00:00
snyk-bot
595cfc29d4 fix: package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-MACADDRESS-567156
2020-05-05 10:45:20 +02:00
mhsjlw
2fa2d2b929
Add NOTICE regarding current state 2019-07-14 13:19:25 -04:00
Filiph Sandström
c75a661a48 Build package before commit. 2019-07-14 18:22:34 +02:00
Filiph Sandström
06c77c3668 Manually fix packets for 1.12 2019-07-14 17:53:52 +02:00
Filiph Sandström
b7e39dec84 Add babel for esnext support. 2019-07-14 17:21:20 +02:00
Filiph Sandström
9c4dec74ca
Update protocol.json 2019-07-12 02:55:50 +02:00
Filiph Sandström
7038f7a0f0
Update package.json 2018-04-22 18:47:47 +02:00
Filiph Sandström
894f2c949e Updated link to convertor. 2017-07-01 02:12:50 +02:00
Filiph Sandström
bb13b9c719 Fix indention. 2017-06-23 19:56:52 +02:00
Filiph Sandström
8d735acbf9 Read error for params.mcpe.name : 120 is not in the mappings value 2017-06-23 08:03:29 +02:00
Filiph Sandström
7c17673f3f TODO 2017-06-18 17:54:02 +02:00
mhsjlw
acafc8df1b Update README.md 2017-04-15 07:46:55 -04:00
mhsjlw
d61ddd7d20 oops 2017-01-22 13:59:07 -05:00
mhsjlw
a244ed62df skin data 2017-01-22 13:44:21 -05:00
mhsjlw
166960e257 skin 2017-01-22 13:41:45 -05:00
mhsjlw
a187f0a9be updates ! 2017-01-22 13:28:23 -05:00
Filiph Sandström
c3671edc01 Fix minor issue. 2017-01-21 12:44:12 +01:00
Filiph Sandström
dd43932720 Update protocol.json 2017-01-20 09:44:22 +01:00
Filiph Sandström
5c6042b61f *faceplam* 2017-01-20 09:21:05 +01:00
Filiph Sandström
239acc73b4 Update createServer.js 2017-01-20 09:18:06 +01:00
Filiph Sandström
e48f146528 Add writePacket and writeData. 2017-01-18 09:26:56 +01:00
Filiph Sandström
ae22c37c20 Changed order of "Contributors".
Let's face it, they've done a lot more than I'll eve be able to do for this project. Also, most likely they're a lot better looking than me too; so there's that.
2017-01-17 23:10:42 +01:00
Filiph Sandström
82720980f8 Merge pull request #2 from filfat/rom1504-patch-1
adapt chat
2017-01-17 16:06:12 +01:00
Romain Beaumont
24ef9c5da1 adapt chat
except translate
see https://github.com/NiclasOlofsson/MiNET/blob/master/src/MiNET/MiNET/Net/McpeText.cs
2017-01-17 16:05:15 +01:00
Filiph Sandström
1dae81fcb2 Retracted the ABRMS license.
I still don't like you RMS though.
2016-12-29 16:19:41 +01:00
Filiph Sandström
151be30ca5 Update README.md 2016-12-29 16:06:03 +01:00
Filiph Sandström
0c1bf07d0c Fix encryption toggle. 2016-12-20 18:23:26 +01:00
Filiph Sandström
0ed2e7b61c Remove unused modules. 2016-12-20 18:22:37 +01:00
Filiph Sandström
74a3e6db45 Add documentation.
Add encryption toggle.
2016-12-20 17:42:58 +01:00
Filiph Sandström
f432468de3 1.0 2016-12-17 18:25:20 +01:00
Filiph Sandström
d478b66324 Update README.md 2016-12-14 18:22:24 +01:00
Filiph Sandström
710f4372e6 Updated to automatically generated protocol. 2016-12-14 18:19:16 +01:00
Filiph Sandström
0094941fd5 Cleanup create server. 2016-12-12 08:44:19 +01:00
Filiph Sandström
0af8627d23 Update protocol.json 2016-12-11 21:43:29 +01:00
Filiph Sandström
559942de75 Update protocol.json 2016-12-11 19:13:32 +01:00
Filiph Sandström
6332fe5072 Add minor fixes. 2016-12-11 00:33:24 +01:00
Filiph Sandström
332d0a5e49 Update package.json 2016-12-05 20:57:48 +01:00
Filiph Sandström
2737203101 Update protodef 2016-12-05 20:40:32 +01:00
Filiph Sandström
8e920e8399 Update start game (doesn't work yet, need varlong) 2016-10-29 18:03:00 +02:00
Filiph Sandström
7739d5c810 Fix login packet. 2016-10-29 17:05:08 +02:00
Filiph Sandström
89a63bb137 Apparently it's a byte array now. 2016-10-27 10:42:08 +02:00
Filiph Sandström
a1b4c6b02b Getting response now. 2016-10-27 10:38:48 +02:00
Filiph Sandström
ed6870f8d5 Update package.json 2016-10-26 22:36:32 +02:00
Filiph Sandström
44b262294d Add missing packets (doesn't work yet). 2016-10-26 22:06:45 +02:00
Filiph Sandström
d910a96ee3 Bump version 2016-10-26 15:05:42 +02:00
Filiph Sandström
c7d9191420 Update to 4 tabs. 2016-10-26 15:05:07 +02:00
Filiph Sandström
5f42967845 Update README.md 2016-10-26 14:34:46 +02:00
Filiph Sandström
b4ed008d64 Update mappings to 0.16. 2016-10-26 14:34:00 +02:00
Filiph Sandström
5b1f1ab162 Update protocol.json 2016-06-23 22:03:52 +02:00
Filiph Sandström
82fba41ae1 Update createServer.js 2016-06-23 22:03:37 +02:00
Filiph Sandström
947a5e0872 Update createServer.js 2016-06-23 20:53:19 +02:00
Filiph Sandström
78cfd29aab Update protocol.json 2016-06-23 04:12:09 +02:00
Romain Beaumont
d8ee61b7db worldName in server.js 2016-06-21 03:03:52 +02:00
Romain Beaumont
315e32d634 string of start_game is worldName (according to Intyre) 2016-06-21 03:02:44 +02:00
Romain Beaumont
d040667bc0
spawns ! 2016-06-18 15:51:41 +02:00
Romain Beaumont
66111dad54
encryption + complete example. The ideas are there, but it doesn't seems to be accepted by the client yet 2016-06-18 14:58:17 +02:00
Romain Beaumont
2e1e3f8bb2
update raknet 2016-06-18 14:37:37 +02:00
Romain Beaumont
2c6ee76152
client_to_server_handshake doesn't have the magic 2016-06-18 14:37:23 +02:00
Romain Beaumont
b86eb6a80f
add rest of the parsing pipeline. Works but the client is sending unparsable packet in a batch 2016-06-18 13:07:13 +02:00
Romain Beaumont
6478e625ec
read/write X509 properly, fix decrypt 2016-06-17 15:39:36 +02:00
Romain Beaumont
7bf93ae68b
formatting fixes 2016-06-17 15:19:29 +02:00
Romain Beaumont
549f8dc084
fix secretKeyBytes a bit 2016-06-17 15:07:45 +02:00
Romain Beaumont
3dfc5134b6
add checksum test 2016-06-17 15:06:22 +02:00
Romain Beaumont
7cf474a49f
add test for ecdh : values might not be correct 2016-06-17 14:55:15 +02:00
Romain Beaumont
d22a813a23
add decryption test 2016-06-17 14:33:55 +02:00
mhsjlw
709a456ecd decrypt our first packet 2016-06-16 08:14:33 -04:00
mhsjlw
26dca46471 more encryption stuff, lots of progress 2016-06-16 07:11:22 -04:00
mhsjlw
54c371dd62 generating public keys 2016-06-15 07:30:02 -04:00
mhsjlw
1dc00237fa login event, jwt decoding 2016-06-15 06:53:00 -04:00
Romain Beaumont
8c06ca5a38
parse server bound batches 2016-06-15 00:44:38 +02:00
mhsjlw
6783fa43cf fix datatypes, we ain't got no short/long 2016-06-14 18:41:49 -04:00
mhsjlw
97c8b7d0aa change packet prefix, remove duplicate packets 2016-06-14 18:40:20 -04:00
mhsjlw
8c30673c6c update all examples 2016-06-14 18:17:54 -04:00
mhsjlw
f07c69176e remove a bunch of errors in the protocol and keep the standard 2016-06-14 18:00:29 -04:00
mhsjlw
712d44bcad oops, fix one protocol error 2016-06-14 17:51:49 -04:00
mhsjlw
5b9ec7a1b6 merge protocol with the mc-data to make fields in-sync 2016-06-14 17:47:33 -04:00
mhsjlw
39bf3d6aba update protocol to 0.15 and start jwt decryption 2016-06-14 07:51:41 -04:00
Romain Beaumont
de05863815 use mcdata 2016-06-09 20:26:23 -04:00
mhsjlw
8109dbcdd8 update version to 2.2.3 2016-05-23 07:28:36 -04:00
mhsjlw
7ea94c355d fix use item packet 2016-05-23 07:27:26 -04:00
74 changed files with 5328 additions and 2322 deletions

3
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,3 @@
github: PrismarineJS
open_collective: prismarinejs
custom: https://rysolv.com/repos/detail/74691b23-938d-4b2f-b65a-5c47bf5b3f0f

18
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,18 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: raknet-native
versions:
- 0.2.0
- 1.0.0
- dependency-name: bedrock-provider
versions:
- 1.0.0
- dependency-name: mocha
versions:
- 8.3.1

139
.github/helper-bot/index.js vendored Normal file
View file

@ -0,0 +1,139 @@
// Automatic version update checker for Minecraft bedrock edition.
const fs = require('fs')
const cp = require('child_process')
const helper = require('gh-helpers')()
const latestVesionEndpoint = 'https://itunes.apple.com/lookup?bundleId=com.mojang.minecraftpe&time=' + Date.now()
const changelogURL = 'https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs'
// Relevant infomation for us is:
// "version": "1.17.10",
// "currentVersionReleaseDate": "2021-07-13T15:35:49Z",
// "releaseNotes": "What's new in 1.17.10:\nVarious bug fixes",
function buildFirstIssue (title, result, externalPatches) {
let commitData = ''
let protocolVersion = '?'
const date = new Date(result.currentVersionReleaseDate).toUTCString()
for (const name in externalPatches) {
const [patches, diff] = externalPatches[name]
commitData += '### ' + name + '\n'
for (const [name, url] of patches) {
commitData += `<a href="${url}">${name}</a>\n`
}
if (diff) commitData += `\n**[See the diff between *${result.currentVersionReleaseDate}* and now](${diff})**\n`
else commitData += '\n(No changes so far)\n'
}
try { protocolVersion = getProtocolVersion() } catch (e) { console.log(e) }
return {
title,
body: `
A new Minecraft Bedrock version is available (as of ${date}), version **${result.version}**
## Official Changelog
* ${result.releaseNotes} *(via App Store)*
* ${changelogURL}
## 3rd party protocol patches
${commitData}
## Protocol Details
(I will close this issue automatically if "${result.version}" is added to index.d.ts on "master" and there are no X's below)
<table>
<tr><td><b>Name</b></td><td>${result.version}</td>
<tr><td><b>Protocol ID</b></td><td>${protocolVersion}</td>
<!-- TODO ... automatically fetch server, test and grab relevant information and dump
<tr><td><b>Partly Already Compatible</b></td><td></td>
<tr><td><b>Protocol Dumpers Work</b></td><td></td>
-->
</table>
-----
🤖 I am a bot, I check for updates every 2 hours without a trigger. You can close this issue to prevent any further updates.
`
}
}
function getCommitsInRepo (repo, containing, since) {
const endpoint = `https://api.github.com/repos/${repo}/commits`
console.log('Getting', endpoint)
cp.execSync(`curl -L ${endpoint} -o commits.json`, { stdio: 'inherit', shell: true })
const commits = JSON.parse(fs.readFileSync('./commits.json', 'utf-8'))
const relevant = []
for (const commit of commits) {
if (commit.commit.message.includes(containing)) {
console.log('commit url', commit.html_url)
relevant.push([commit.commit.message, commit.html_url])
}
}
if (since) {
cp.execSync(`curl -L ${endpoint}?since=${since} -o commits.json`, { stdio: 'inherit', shell: true })
const commits = JSON.parse(fs.readFileSync('./commits.json', 'utf-8'))
if (commits.length) {
const head = commits[0].sha
const tail = commits[commits.length - 1].sha
return [relevant, `https://github.com/${repo}/compare/${tail}..${head}`]
}
}
return [relevant]
}
function getProtocolVersion () {
if (!fs.existsSync('./ProtocolInfo.php')) cp.execSync('curl -LO https://raw.githubusercontent.com/pmmp/PocketMine-MP/stable/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php', { stdio: 'inherit', shell: true })
const currentApi = fs.readFileSync('./ProtocolInfo.php', 'utf-8')
const [, latestProtocolVersion] = currentApi.match(/public const CURRENT_PROTOCOL = (\d+);/)
return latestProtocolVersion
}
async function fetchLatest () {
if (!fs.existsSync('./results.json')) cp.execSync(`curl -L "${latestVesionEndpoint}" -o results.json`, { stdio: 'inherit', shell: true })
const json = require('./results.json')
const result = json.results[0]
// console.log(json)
if (!fs.existsSync('./index.d.ts')) cp.execSync('curl -LO https://raw.githubusercontent.com/PrismarineJS/bedrock-protocol/master/index.d.ts', { stdio: 'inherit', shell: true })
const currentApi = fs.readFileSync('./index.d.ts', 'utf-8')
const supportedVersions = currentApi.match(/type Version = ([^\n]+)/)[1].replace(/\||'/g, ' ').split(' ').map(k => k.trim()).filter(k => k.length)
console.log(supportedVersions)
let { version, currentVersionReleaseDate, releaseNotes } = result
console.log(version, currentVersionReleaseDate, releaseNotes)
const title = `Support Minecraft ${result.version}`
const issueStatus = await helper.findIssue({ titleIncludes: title }) || {}
if (supportedVersions.includes(version)) {
if (issueStatus.isOpen) {
helper.close(issueStatus.id, `Closing as ${version} is now supported`)
}
console.log('Latest version is supported.')
return
}
if (issueStatus.isClosed) {
// We already made an issue, but someone else already closed it, don't do anything else
console.log('I already made an issue, but it was closed')
return
}
version = version.replace('.0', '')
const issuePayload = buildFirstIssue(title, result, {
PocketMine: getCommitsInRepo('pmmp/PocketMine-MP', version, currentVersionReleaseDate),
gophertunnel: getCommitsInRepo('Sandertv/gophertunnel', version, currentVersionReleaseDate),
CloudburstMC: getCommitsInRepo('CloudburstMC/Protocol', version, currentVersionReleaseDate)
})
if (issueStatus.isOpen) {
helper.updateIssue(issueStatus.id, issuePayload)
} else {
helper.createIssue(issuePayload)
}
fs.writeFileSync('./issue.md', issuePayload.body)
console.log('OK, wrote to ./issue.md', issuePayload)
}
fetchLatest()

36
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: CI
on:
push:
branches: [ '*', '!gh-pages' ]
pull_request:
branches: [ '*', '!gh-pages' ]
workflow_dispatch:
inputs:
via:
description: 'trigger origin'
required: true
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [22.x]
runs-on: ${{ matrix.os }}
timeout-minutes: 14
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
# Old versions of bedrock use old libssl that Ubuntu no longer ships with; need manual install
- name: (Linux) Install libssl 1.1
if: runner.os == 'Linux'
run: |
wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb
sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb
- run: npm install
- run: npm test

22
.github/workflows/commands.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Repo Commands
on:
issue_comment: # Handle comment commands
types: [created]
pull_request_target: # Handle renamed PRs
types: [edited]
jobs:
comment-trigger:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
- name: Run command handlers
uses: PrismarineJS/prismarine-repo-actions@master
with:
# NOTE: You must specify a Personal Access Token (PAT) with repo access here. While you can use the default GITHUB_TOKEN, actions taken with it will not trigger other actions, so if you have a CI workflow, commits created by this action will not trigger it.
token: ${{ secrets.PAT_PASSWORD }}
# See `Options` section below for more info on these options
install-command: npm install
/fixlint.fix-command: npm run fix

33
.github/workflows/publish.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: npm-publish
on:
push:
branches:
- master # Change this to your default branch
jobs:
npm-publish:
name: npm-publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@master
- name: Set up Node.js
uses: actions/setup-node@master
with:
node-version: 14.0.0
- run: npm install
- id: publish
uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_AUTH_TOKEN }}
- name: Create Release
if: steps.publish.outputs.type != 'none'
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.publish.outputs.version }}
release_name: Release ${{ steps.publish.outputs.version }}
body: ${{ steps.publish.outputs.version }}
draft: false
prerelease: false

24
.github/workflows/update-helper.yml vendored Normal file
View file

@ -0,0 +1,24 @@
name: Update Helper
on:
workflow_dispatch:
schedule:
- cron: "0 */2 * * *"
jobs:
helper:
name: update-checker
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@master
- name: Set up Node.js
uses: actions/setup-node@master
with:
node-version: 22.0.0
- name: Install Github Actions helper
run: npm i gh-helpers
# The env vars contain the relevant trigger information, so we don't need to pass it
- name: Runs helper
run: cd .github/helper-bot && node index.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

8
.gitignore vendored
View file

@ -1,2 +1,8 @@
node_modules/
npm-debug.log
npm-debug.log
package-lock.json
__*
# Runtime generated data
data/
tools/bds*
tools/pmmp*

4
.gitpod Normal file
View file

@ -0,0 +1,4 @@
image:
file: .gitpod.DockerFile
tasks:
- command: npm install

8
.gitpod.DockerFile Normal file
View file

@ -0,0 +1,8 @@
FROM gitpod/workspace-full:latest
RUN bash -c ". .nvm/nvm.sh \
&& nvm install 14 \
&& nvm use 14 \
&& nvm alias default 14"
RUN echo "nvm use default &>/dev/null" >> ~/.bashrc.d/51-nvm-fix

10
.npmignore Normal file
View file

@ -0,0 +1,10 @@
node_modules/
npm-debug.log
__*
# Runtime generated data
data/
tools/bds*
# Extra data
examples
test
.github

1
.npmrc Normal file
View file

@ -0,0 +1 @@
package-lock=false

View file

@ -1,3 +1,265 @@
## 3.49.0
* [1.21.111 (#649)](https://github.com/PrismarineJS/bedrock-protocol/commit/b48518a6e79e72101fe7136433cbd6277339fc5c) (thanks @Slauh)
* [Skin Data Changes (#647)](https://github.com/PrismarineJS/bedrock-protocol/commit/407756b93880cdda4fdbff194fc4163ceedf4e82) (thanks @thejfkvis)
## 3.48.1
* [Update login client skinData (#635)](https://github.com/PrismarineJS/bedrock-protocol/commit/6b1474d2c6f93b47dee9d4816de59579f82ed5a9) (thanks @TSL534)
## 3.48.0
* [1.21.100 (#632)](https://github.com/PrismarineJS/bedrock-protocol/commit/06fb3de3a0023d03201dbcee7e9178c269462766) (thanks @extremeheat)
## 3.47.0
* [1.21.93 support (#623)](https://github.com/PrismarineJS/bedrock-protocol/commit/14daa2d95aac90ffcc7b42d625e270020ec2f162) (thanks @CreeperG16)
## 3.46.0
* [1.21.90 support (#617)](https://github.com/PrismarineJS/bedrock-protocol/commit/c66cdd3d62d2fa9c581693d8c70d7b41f355b63e) (thanks @CreeperG16)
## 3.45.0
* [1.21.80 (#602)](https://github.com/PrismarineJS/bedrock-protocol/commit/e71fd513ddbd432983f221980080b61e11576965) (thanks @extremeheat)
## 3.44.0
* [1.21.70 (#594)](https://github.com/PrismarineJS/bedrock-protocol/commit/065f41db8cfc8cbd8106bd9e376c899ec25f3f77) (thanks @CreeperG16)
## 3.43.1
* [Fix server not correctly removing clients (#588)](https://github.com/PrismarineJS/bedrock-protocol/commit/47f342ca958ba87a7719783bd5c855cebdd4aa65) (thanks @EntifiedOptics)
## 3.43.0
* [1.21.60 support (#570)](https://github.com/PrismarineJS/bedrock-protocol/commit/eeb5e47e35f31cc571a9a8a491f5a89b27e637f1) (thanks @CreeperG16)
* [Fix version feature handling (#572)](https://github.com/PrismarineJS/bedrock-protocol/commit/0ed8e32be85f05926cd97d5f0317ed004ae5eefa) (thanks @ItsMax123)
## 3.42.3
* [Fix Server `maxPlayers` option (#565)](https://github.com/PrismarineJS/bedrock-protocol/commit/38dc5a256105a44786d5455570d5a130e64ef561) (thanks @extremeheat)
## 3.42.2
* Fix missing type serialization error
## 3.42.1
* [Add 1.21.40 login fields (#553)](https://github.com/PrismarineJS/bedrock-protocol/commit/24d3200181c060162b04fb233fef6e0d6d1a93aa) (thanks @extremeheat)
* [Remove protodef varint types (#552)](https://github.com/PrismarineJS/bedrock-protocol/commit/347e303ce422bdb6f6dfd4cba57d7d3937214707) (thanks @extremeheat)
## 3.42.0
* [1.21.50 support](https://github.com/PrismarineJS/bedrock-protocol/commit/1c0836bff03d50cb12a3e45763eac6c9f605e00c) (thanks @extremeheat)
* [Dynamic compression & batch header (#544)](https://github.com/PrismarineJS/bedrock-protocol/commit/911e0e890febc00102cd1e5406731e66f7bad0ef) (thanks @LucienHH)
## 3.41.0
* [1.21.42 support](https://github.com/PrismarineJS/bedrock-protocol/commit/dd5c4de4f2624c3654af66e9a40a65eb13de0850) (thanks @CreeperG16)
## 3.40.0
* [1.21.30 support (#527)](https://github.com/PrismarineJS/bedrock-protocol/commit/fc30c96135ec20dca1257f702152cba61d4a59be) (thanks @pokecosimo)
* [Update tests (#528)](https://github.com/PrismarineJS/bedrock-protocol/commit/cb530c8b45bf505f75e0e39241d88085c5564ae8) (thanks @extremeheat)
## 3.39.0
* [1.21.20](https://github.com/PrismarineJS/bedrock-protocol/commit/3be55777fab4949179d3a7108ee29bbd8fada5a7) (thanks @extremeheat)
* [update disconnect packet](https://github.com/PrismarineJS/bedrock-protocol/commit/4c3f62567e0f6ce20b70ea23238fce8606011e95) (thanks @extremeheat)
## 3.38.0
* [Support 1.21.2, and add missing versions to type definitions (#510)](https://github.com/PrismarineJS/bedrock-protocol/commit/5d3986924d3f262708d7c7e55a7f410f12c7903c) (thanks @CreeperG16)
* [Fix example in README.md for 1.21 (#506)](https://github.com/PrismarineJS/bedrock-protocol/commit/c4593aa355d6ce9e2ac65cc2102cd9285a6b6449) (thanks @Ant767)
* [Don't send now deprecated tick sync packets on 1.21 and newer (#504)](https://github.com/PrismarineJS/bedrock-protocol/commit/84c5231b92df9f5f1a09b29a05e7abfed62f1c2b) (thanks @w0ahL)
## 3.37.0
* [Support 1.21.0](https://github.com/PrismarineJS/bedrock-protocol/commit/5b2d78792c9b4c070d727a9028a6b3a266483e1c) (thanks @CreeperG16)
* [Fix typo in types (#501)](https://github.com/PrismarineJS/bedrock-protocol/commit/16e15d80a5084a19ed2fbabc023789ee38922b3a) (thanks @Kaaaaii)
## 3.36.0
* [Support 1.20.80](https://github.com/PrismarineJS/bedrock-protocol/commit/bd32aa8d04555fa2fdc4ecd6abbeb6124e2ae8bb) (thanks @extremeheat)
## 3.35.0
* [Support 1.20.71](https://github.com/PrismarineJS/bedrock-protocol/commit/d8e707112acc038b6c9564d9a21b2f977326e47f) (thanks @extremeheat)
* [Note `npm update` command in readme](https://github.com/PrismarineJS/bedrock-protocol/commit/ab93d0d0824bd0ace250fb73f703dc7b60ecd780) (thanks @extremeheat)
## 3.34.0
* [1.20.61 support (#480)](https://github.com/PrismarineJS/bedrock-protocol/commit/c278a03f952d23320b80f8c09b6372d41eeff26a) (thanks @extremeheat)
* [Compressor handling update for 1.20.60 (#479)](https://github.com/PrismarineJS/bedrock-protocol/commit/d3161badc65f2eba4b6e7c9e974ca4e3529a7e94) (thanks @extremeheat)
* [Update and rename CONTRIBUTING.md to docs/CONTRIBUTING.md (#475)](https://github.com/PrismarineJS/bedrock-protocol/commit/be6f0cde9f7970a4f9aa376c589c58d8cb4187c3) (thanks @extremeheat)
* [Add flow and deviceType options to relay (#464)](https://github.com/PrismarineJS/bedrock-protocol/commit/842e66266f09e8670a644a618d0ac4157746cd43) (thanks @GameParrot)
## 3.33.1
* [Fix zigzag type move in prismarine-nbt (#471)](https://github.com/PrismarineJS/bedrock-protocol/commit/7b74cbf7129646adc80d50304afce6240848cfae) (thanks @extremeheat)
## 3.33.0
* [1.20.50 (#466)](https://github.com/PrismarineJS/bedrock-protocol/commit/d53211c6a1fe5f941ce547886ad6ec031ae05d9d) (thanks @extremeheat)
* [Add 1.20.30 and 1.20.40 to index.d.ts (#461)](https://github.com/PrismarineJS/bedrock-protocol/commit/2ecf01d63e64b910b87f303fc4fb2b30f392cb28) (thanks @CreeperG16)
## 3.32.0
* [1.20.40 support (#459)](https://github.com/PrismarineJS/bedrock-protocol/commit/63eb673c1f30beb58f97e3b37295129000bf6a10) (thanks @CreeperG16)
* [Update Minecraft wiki link to new domain (#455)](https://github.com/PrismarineJS/bedrock-protocol/commit/689658c4ab1ccb3ef1ae812d78d090212b1acf3f) (thanks @Spongecade)
## 3.31.0
* [1.20.30](https://github.com/PrismarineJS/bedrock-protocol/commit/22502b90fdc29f6327239c6c201370c8f839c892) (thanks @extremeheat)
* [Add links field to server resource_packs_info](https://github.com/PrismarineJS/bedrock-protocol/commit/f92db61c89851dfbdbc906f926fc1433162854d0) (thanks @extremeheat)
* [Update API.md (#448)](https://github.com/PrismarineJS/bedrock-protocol/commit/8f3b6c5aecf24d6f8d235afe2a9d911840e6a3f8) (thanks @Laamy)
## 3.30.1
* [Update Mojang public key used for logins (#443)](https://github.com/PrismarineJS/bedrock-protocol/commit/f0f1351d40966192e38ee9fe21b7c37754abba04) (thanks @GameParrot)
* [index.d.ts: Fixed a typo (#441)](https://github.com/PrismarineJS/bedrock-protocol/commit/2c00402a9e9a0a283e712bf4f52190a57ea12c3f) (thanks @kotinash)
* [Mark `listen` and `close` as async (#440)](https://github.com/PrismarineJS/bedrock-protocol/commit/50cd489f6e16fa6fe04b1825617d8246bd3935f4) (thanks @MrSterdy)
* [Stop disconnecting when upstream packet deserialization fails (#435)](https://github.com/PrismarineJS/bedrock-protocol/commit/141442057464b3247ace8468863f27a3c334306e) (thanks @MrSterdy)
* [Add 1.20.0 and 1.20.10 to index.d.ts (#431)](https://github.com/PrismarineJS/bedrock-protocol/commit/010d57e78a9130c612e48db7a32f841de83e9c68) (thanks @CreeperG16)
## 3.30.0
* 1.20.10 support (thanks @CreeperG16)
* [Fix upstream relay batchingInterval (#425)](https://github.com/PrismarineJS/bedrock-protocol/commit/b2c141c25f3fad9641644742b6cc1a71bc601d61) (thanks @GameParrot)
## 3.29.1
* Add missing data to client login user chain (#420)
* Add FAQ entry and replit warning on client ping error (#415)
* Types: Fix Relay authTitle type (#418)
## 3.29.0
* 1.20.0 support
## 3.28.1
* Fix `followPort` option (@LucienHH)
* Typescript definition fixes (@hvlxh)
## 3.28.0
* 1.19.80 support
## 3.27.1
* Fix `raknetBackend` option not being applied correctly
## 3.27.0
* Corrections to types (@stevarino)
* Expose ServerAdvertisement class (#368) @hvlxh
* Update mc-data links
## 3.26.0
* 1.19.70 support (@CreeperG16)
* types: add some type hints (#354) @hvlxh
## 3.25.0
* 1.19.63 support (@stevarino)
* Add close packet in server player API doc (#347) @hvlxh
## 3.24.0
* 1.19.62 support (@CreeperG16)
## 3.23.0
* 1.19.60 support (@CreeperG16)
* added onMsaCode, profilesFolder to ClientOptions (@jarco-dev)
## 3.22.0
* 1.19.50 support (@WillQizza)
## 3.21.0
* 1.19.40 support (#314)
* types: Fix missing field in ServerAdvertisement (#313) (@minerj101)
## 3.20.1
* Fix buffer length calculation in ServerAdvertisement (#292) (thanks @KurtThiemann)
* Handle Relay serialization errors by kicking (#290)
## 3.20.0
* Preliminary 1.19.30 support, improve error handling and server pong data (#284)
## 3.19.0
* Add option for port redirection, fix Realm handling (#282)
* Add Port Redirect Functionality (#278) @stevarino
* Add Get-AppxPackage command to FAQ.md (#276) @stevarino
* Remove viewer example
## 3.18.0
* 1.19.21 support (#266)
## 3.17.0
* relay: Add multi-user login support (#258)
* Add fields from 1.19.20 to login chain data (#259) @CleSucre
* Fix nbt encoding size on single null tag NBT (#264)
* test: Add -u flag unzipping vanilla server (#262)
## 3.16.0
* 1.19.20 support (#251)
* Add new raknet library option (raknet-node) (#211) @b23r0
## 3.15.0
* 1.19.10 support
* Remove Realm fetch when joining via invite (#228) @LucienHH
* Add Realm support to Relay (#226) @ATXLtheAxolotl
## 3.14.0
* 1.19 support
* Better handle ping timeout, update documentation (#218) @stevarino
## 3.13.0
* Update API documentation
* Emit generic 'packet' event for server clients (#205) @ATXLtheAxolotl
* Add XUID field for client offline mode client chain (#203)
## 3.12.0
* 1.18.30 support
## 3.11.1
* Bump minecraft-data version
## 3.11.0
* Implement Realm joining (#193) @LucienHH
* Refactor client connection sequence (#189) @extremeheat
* Add profilesFolder to Relay (#192) @CreeperG16
* Emit error from relay when server can't be pinged (#191)
* Pass relay onMsaCode to client (#190) @Heath123
* Mark raknet-native as required dependency (#188)
* Ignore unconnected packets, remove babel (#185)
## 3.10.0
* Support 1.18.11 (#179) @extremeheat
* Switch to sync zlib with 512k chunks, adjustable compression level (#174) @extremeheat
## 3.9.0
* Proxy fixes, logging and doc updates [#169](https://github.com/PrismarineJS/bedrock-protocol/pull/169)
## 3.8.0
* 1.18.0 support
## 3.7.0
* 1.17.40 support
## 3.6.0
* 1.17.30 support
* minecraft-data used for protocol data
## 3.5.1
* Fix 1.17.10 npc packet serialization (#119)
## 3.5.0
* Add 1.17.10 support [#109](https://github.com/PrismarineJS/bedrock-protocol/pull/109)
* You can switch to the JS implementation of raknet by setting `useNativeRaknet: false` in options.
## 3.4.0
* Initial 1.17 support [#99](https://github.com/PrismarineJS/bedrock-protocol/pull/99)
* update connect version based on ping response & fix typings (u9g) [#101](https://github.com/PrismarineJS/bedrock-protocol/pull/101)
* fix: ping types. (JammSpread) [#100](https://github.com/PrismarineJS/bedrock-protocol/pull/100)
## 3.3.0
* Protocol updates for 1.16, with some minor breaking changes to protocol fields [#95](https://github.com/PrismarineJS/bedrock-protocol/pull/95)
* Fix npm install issues
## 3.2.1
* Add `authTitle` option to Relay proxy [#92](https://github.com/PrismarineJS/bedrock-protocol/pull/92)
* Protocol, type definition fixes
## 3.2.0
* Fix empty chunks on proxy spawn [#89](https://github.com/PrismarineJS/bedrock-protocol/pull/89)
* Send skin data to server [#88](https://github.com/PrismarineJS/bedrock-protocol/pull/88)
* Support xbox title + live.com auth [#86](https://github.com/PrismarineJS/bedrock-protocol/pull/86)
* Protocol updates and fixes
* Fix third party servers, optional client encryption [#83](https://github.com/PrismarineJS/bedrock-protocol/pull/83)
## 3.1.0
* Add support for 1.16
* New docs and examples
* Ping support
* Add microsoft authentication
* Codebase refactor
## 2.4.0
* Update to version 1.12.0
* Add option to provide protocol.json
## 2.2.3
* fix the use item packet
## 2.2.2
* fix the block update packet, for real this time

144
README.md
View file

@ -1,27 +1,139 @@
pocket-minecraft-protocol
=========================
# bedrock-protocol
[![NPM version](https://img.shields.io/npm/v/bedrock-protocol.svg)](http://npmjs.com/package/bedrock-protocol)
[![Build Status](https://github.com/PrismarineJS/bedrock-protocol/workflows/CI/badge.svg)](https://github.com/PrismarineJS/bedrock-protocol/actions?query=workflow%3A%22CI%22)
[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/PrismarineJS/bedrock-protocol)
[![NPM version](https://img.shields.io/npm/v/pocket-minecraft-protocol.svg)](http://npmjs.com/package/pocket-minecraft-protocol)
[![Join the chat at https://gitter.im/mhsjlw/pocket-minecraft-protocol](https://badges.gitter.im/mhsjlw/pocket-minecraft-protocol.svg)](https://gitter.im/mhsjlw/pocket-minecraft-protocol?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Official Discord](https://img.shields.io/static/v1.svg?label=OFFICIAL&message=DISCORD&color=blue&logo=discord&style=for-the-badge)](https://discord.gg/GsEFRM8)
Parse and serialize Minecraft PE packets.
Minecraft Bedrock Edition (aka MCPE) protocol library, supporting authentication and encryption. Help [contribute](docs/CONTRIBUTING.md).
[Protocol doc](https://prismarinejs.github.io/minecraft-data/?v=bedrock_1.19.10&d=protocol)
## Features
* Supports Minecraft PE `0.14.3`
* Pure JavaScript
* Easily send and listen for any packet
* RakNet support through [node-raknet](https://github.com/mhsjlw/node-raknet)
- Supports Minecraft Bedrock version 1.16.201, 1.16.210, 1.16.220, 1.17.0, 1.17.10, 1.17.30, 1.17.40, 1.18.0, 1.18.11, 1.18.30, 1.19.1, 1.19.10, 1.19.20, 1.19.21, 1.19.30, 1.19.40, 1.19.41, 1.19.50, 1.19.60, 1.19.62, 1.19.63, 1.19.70, 1.19.80, 1.20.0, 1.20.10, 1.20.30, 1.20.40, 1.20.50, 1.20.61, 1.20.71, 1.20.80, 1.21.0, 1.21.2, 1.21.21, 1.21.30, 1.21.42, 1.21.50, 1.21.60, 1.21.70, 1.21.80, 1.21.90, 1.21.93, 1.21.100, 1.21.111
- Parse and serialize packets as JavaScript objects
- Automatically respond to keep-alive packets
- [Proxy and mitm connections](docs/API.md#proxy-docs)
- Client
- Authentication
- Encryption
- [Ping a server for status](docs/API.md#beping-host-port---serveradvertisement)
- Server
- Autheticate clients with Xbox Live
- Ping status
* Robust test coverage.
* Easily extend with many other PrismarineJS projects, world providers, and more
* Optimized for rapidly staying up to date with Minecraft protocol updates.
Want to contribute on something important for PrismarineJS ? go to https://github.com/PrismarineJS/mineflayer/wiki/Big-Prismarine-projects
## Installation
Simply run
npm install pocket-minecraft-protcol
`npm install bedrock-protocol`
Then view our `examples` for inspiration!
To update bedrock-protocol (or any Node.js package) and its dependencies after a previous install, you must run `npm update --depth 9999`
## Contributors
This project is run by these guys:
## Usage
- [mhsjlw](https://github.com/mhsjlw)
- [rom1504](https://github.com/rom1504)
### Client example
Example to connect to a server in offline mode, and relay chat messages back:
```js
const bedrock = require('bedrock-protocol')
const client = bedrock.createClient({
host: 'localhost', // optional
port: 19132, // optional, default 19132
username: 'Notch', // the username you want to join as, optional if online mode
offline: true // optional, default false. if true, do not login with Xbox Live. You will not be asked to sign-in if set to true.
})
client.on('text', (packet) => { // Listen for chat messages from the server and echo them back.
if (packet.source_name != client.username) {
client.queue('text', {
type: 'chat', needs_translation: false, source_name: client.username, xuid: '', platform_chat_id: '', filtered_message: '',
message: `${packet.source_name} said: ${packet.message} on ${new Date().toLocaleString()}`
})
}
})
```
### Client example joining a Realm
Example to connect to a Realm that the authenticating account is owner of or has been invited to:
```js
const bedrock = require('bedrock-protocol')
const client = bedrock.createClient({
realms: {
pickRealm: (realms) => realms[0] // Function which recieves an array of joined/owned Realms and must return a single Realm. Can be async
}
})
```
### Server example
*Can't connect locally on Windows? See the [faq](docs/FAQ.md)*
```js
const bedrock = require('bedrock-protocol')
const server = bedrock.createServer({
host: '0.0.0.0', // optional. host to bind as.
port: 19132, // optional
version: '1.17.10', // optional. The server version, latest if not specified.
})
server.on('connect', client => {
client.on('join', () => { // The client has joined the server.
const d = new Date() // Once client is in the server, send a colorful kick message
client.disconnect(`Good ${d.getHours() < 12 ? '§emorning§r' : '§3afternoon§r'} :)\n\nMy time is ${d.toLocaleString()} !`)
})
})
```
### Ping example
```js
const { ping } = require('bedrock-protocol')
ping({ host: 'play.cubecraft.net', port: 19132 }).then(res => {
console.log(res)
})
```
## Documentation
For documentation on the protocol, and packets/fields see the [protocol documentation](https://prismarinejs.github.io/minecraft-data/protocol).
* See [API documentation](docs/API.md)
* See [frequently asked questions and answers](docs/FAQ.md)
<!-- ## Projects Using bedrock-protocol
* [mineflayer](https://github.com/PrismarineJS/mineflayer/) - create bots with a stable, high level API.
* [pakkit](https://github.com/Heath123/pakkit) To monitor your packets
* [flying-squid](https://github.com/PrismarineJS/flying-squid/) - create minecraft bots with a stable, high level API. -->
## Testing
```npm test```
## Debugging
You can enable some protocol debugging output using `DEBUG` environment variable.
Through node.js, add `process.env.DEBUG = 'minecraft-protocol'` at the top of your script.
## Contribute
Please read [CONTRIBUTING.md](docs/CONTRIBUTING.md) and https://github.com/PrismarineJS/prismarine-contribute
## History
See [history](HISTORY.md)
<!-- ## Related
* [map-colors](https://github.com/AresRPG/aresrpg-map-colors) can be used to convert any image into a buffer of minecraft compatible colors -->

File diff suppressed because it is too large Load diff

229
docs/API.md Normal file
View file

@ -0,0 +1,229 @@
# Documentation
## be.createClient(options) : Client
Returns a `Client` instance and connects to the server.
`options` is an object containing the properties :
| Parameter | Optionality | Description |
| ----------- | ----------- |-|
| host | Conditional | Not required if `realms` is set. host to connect to, for example `127.0.0.1`. |
| port | *optional* | port to connect to, default to **19132** |
| version | *optional* | Version to connect as. If not specified, automatically match server version. |
| offline | *optional* | default to **false**. Set this to true to disable Microsoft/Xbox auth. |
| username | Required | The profile name to connect to the server as. If `offline` set to true, the username that will appear on join, that would normally be the Xbox Gamer Tag. |
| connectTimeout | *optional* | default to **9000ms**. How long to wait in milliseconds while trying to connect to server. |
| onMsaCode | *optional* | Callback called when signing in with a microsoft account with device code auth, `data` is an object documented [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code#device-authorization-response) |
| profilesFolder | *optional* | Where to store cached authentication tokens. Defaults to .minecraft, or the node_modules folder if not found. |
| skipPing | *optional* | Whether pinging the server to check its version should be skipped. |
| followPort | *optional* | Update the options' port parameter to match the port broadcast on the server's ping data (default to true if `realms` not specified) |
| autoInitPlayer | *optional* | default to true, If we should send SetPlayerInitialized to the server after getting play_status spawn. |
| conLog | *optional* | Where to log connection information (server join, kick messages to). Defaults to console.log, set to `null` to not log anywhere. |
| raknetBackend | *optional* | Specifies the raknet implementation to use. Possible options are 'raknet-native' (default, original C++ implementation), 'jsp-raknet' (JS port), and 'raknet-node' (Rust port). Please note when using the non-JS implementation you may the need approporate build tools on your system (for example a C++ or Rust compiler). |
| compressionLevel | *optional* | What zlib compression level to use, default to **7** |
| batchingInterval | *optional* | How frequently, in milliseconds to flush and write the packet queue (default: 20ms) |
| realms | *optional* | An object which should contain one of the following properties: `realmId`, `realmInvite`, `pickRealm`. When defined will attempt to join a Realm without needing to specify host/port. **The authenticated account must either own the Realm or have been invited to it** |
| realms.realmId | *optional* | The id of the Realm to join. |
| realms.realmInvite | *optional* | The invite link/code of the Realm to join. |
| realms.pickRealm | *optional* | A function which will have an array of the user Realms (joined/owned) passed to it. The function should return a Realm. |
*`useNativeRaknet` is deprecated. Setting to true will use 'raknet-native' for `raknetBackend` and setting it to false will use a JavaScript implemenation (jsp-raknet)*
The following special events are emitted by the client on top of protocol packets:
* 'status' - When the client's login sequence status has changed
* 'join' - When the client has joined the server after authenticating
* 'spawn' - When the client has spawned into the game world, as it is getting chunks
* 'kick' - The server has kicked the client
* 'close' - The server has closed the connection
* 'error' - An recoverable exception has happened. Not catching will throw an exception
* 'connect_allowed' - Emitted after the client has pinged the server and gets version information.
* 'heartbeat' - Emitted after two successful tick_sync (keepalive) packets have been sent bidirectionally
* 'packet' - Emitted for all packets received by client
* 'session' - When the client has finished authenticating and connecting
## be.createServer(options) : Server
Returns a `Server` instance and starts listening for clients. All clients will be
authenticated unless offline is set to true.
`options` is an object containing the properties :
| Parameter | Optionality | Description |
| ----------- | ----------- |-|
| host | **Required** | The host to bind to. use `0.0.0.0` to bind all IPv4 addresses. |
| port | *optional* | the port to bind to, default **19132** |
| version | *optional* | Version to run server as. Clients below this version will be kicked, clients above will still be permitted. |
| offline | *optional* | default to **false**. Set this to true to disable Microsoft/Xbox auth enforcement. |
| maxPlayers | *optional* | default to **3**. Set this to change the maximum number of players connected. |
| kickTimeout | *[Future][1]* | How long to wait before kicking a unresponsive client. |
| motd | *optional* | The "message of the day" for the server, the message shown to players in the server list. See usage below. |
| advertisementFn | *optional* | optional. Custom function to call that should return a ServerAdvertisement, used for setting the RakNet server PONG data. Overrides `motd`. |
| conLog | *optional* | Where to log connection information (server join, kick messages to). Default to log only in DEBUG mode. |
| raknetBackend | *optional* | Specifies the raknet implementation to use. Possible options are 'raknet-native' (default, original C++ implementation), 'jsp-raknet' (JS port), and 'raknet-node' (Rust port). Please note when using the non-JS implementation you may the need approporate build tools on your system (for example a C++ or Rust compiler). |
*`useNativeRaknet` is deprecated. Setting to true will use 'raknet-native' for `raknetBackend` and setting it to false will use a JavaScript implemenation (jsp-raknet)*
## be.ping({ host, port }) : ServerAdvertisement
Ping a server and get the response. See type definitions for the structure.
## Server usage
You can create a server as such:
```js
const bedrock = require('bedrock-protocol')
const server = bedrock.createServer({
host: '0.0.0.0', // the host to bind to, use '0.0.0.0' to bind all hosts
port: 19132, // optional, port to bind to, default 19132
offline: false, // default false. verify connections with XBL
motd: {
motd: 'Funtime Server', // Top level message shown in server list
levelName: 'Wonderland' // Sub-level header
}
})
```
Then you can listen for clients and their events:
```js
// The 'connect' event is emitted after a new client has started a connection with the server and is handshaking.
// Its one paramater is the ServerPlayer class instance which handles this players' session from here on out.
server.on('connect', (client) => {
// 'join' is emitted after the client has authenticated & connection is now encrypted.
client.on('join', () => {
// Then we can continue with the server spawning sequence. See examples/serverTest.js for an example spawn sequence.
// ...
// Here's an example of sending a "text" packet, https://prismarinejs.github.io/minecraft-data/?v=bedrock_1.19.60&d=protocol#packet_text
client.queue('text', { type: 'system', message: client.profile.name + ' just joined the server!' })
})
})
```
Server event emissions:
* 'connect' - emitted by `Server` after a client first joins the server. Second paramater is a `ServerPlayer` instance.
'error' event is emitted when a catchable exception happens with a client (for example receiving a bad encrypted packet).
A ServerPlayer instance also emits the following special events:
* 'join' - the client is ready to recieve game packets after successful server-client handshake/encryption
* 'close' - emitted when client quit the server
* 'login' - emitted by client after the client has been authenticated by the server
* 'spawn' - emitted after the client lets the server know that it has successfully spawned
* 'packet' - Emitted for all packets received by client
## Client usage
You can create a client like below:
```js
const bedrock = require('bedrock-protocol')
const client = bedrock.createClient({
host: '127.0.0.1', // the host to bind to, use '0.0.0.0' to bind all hosts
port: 19132, // optional, port to bind to, default 19132
username: 'Notch' // Any profile name, only used internally for account caching when in online mode. In offline mode, the username to connect with.
})
```
```js
// The 'join' event is emitted after the player has authenticated
// and is ready to recieve chunks and start game packets
client.on('join', client => console.log('Player has joined!'))
// The 'spawn' event is emitted. The chunks have been sent and all is well.
client.on('spawn', client => console.log('Player has spawned!'))
// We can listen for text packets. See proto.yml for documentation.
client.on('text', (packet) => {
console.log('Client got text packet', packet)
})
// For example, we can listen to https://prismarinejs.github.io/minecraft-data/?v=bedrock_1.19.60&d=protocol#packet_add_player
// and send them a chat message when a player joins saying hello. Note the lack of the `packet` prefix, and that the packet
// names and as explained in the "Protocol doc" section below, fields are all case sensitive!
client.on('add_player', (packet) => {
client.queue('text', {
type: 'chat', needs_translation: false, source_name: client.username, xuid: '', platform_chat_id: '', filtered_message: '',
message: `Hey, ${packet.username} just joined!`
})
})
```
Order of client event emissions:
* 'connect' - emitted after a client first joins the server
* 'login' - emitted after the client has been authenticated by the server
* 'join' - the client is ready to recieve game packets after successful server-client handshake
* 'spawn' - emitted after the client has permission from the server to spawn
## Methods
[See the type defintions for this library for more information on methods.](../index.d.ts)
Both Client and ServerPlayer classes have `write(name, params)` and `queue(name, params)` methods. The former sends a packet immediately, and the latter queues them to be sent in the next packet batch. Prefer the latter for better performance and less blocking.
You can use `.close()` to terminate a connection, and `.disconnect(reason)` to gracefully kick a connected client.
### Protocol docs
For documentation on the protocol, and packets/fields see the [the protocol doc](https://prismarinejs.github.io/minecraft-data/?v=bedrock_1.18.0&d=protocol) (the emitted event names are the Packet types in lower case without the "packet_" prefix). More information on syntax can be found in CONTRIBUTING.md. When sending a packet, you must fill out all of the required fields.
### Realm docs
To make joining a Realm easier we've added an optional `realm` property to the client. It accepts the following options `realmId`, `realmInvite`, and `pickRealm`, supplying one of these will fetch host/port information for the specified Realm and then attempt to connect the bot.
- `realmId` - The id of the Realm to join.
- `realmInvite` - The invite code/link of the Realm to join.
- `pickRealm` - A function that will be called with a list of Realms to pick from. The function should return the Realm to join.
```js
const bedrock = require('bedrock-protocol')
const client = bedrock.createClient({
realms: {
pickRealm: (realms) => realms[0] // Function which recieves an array of joined/owned Realms and must return a single Realm. Can be async
}
})
```
### Proxy docs
You can create a proxy ("Relay") to create a machine-in-the-middle (MITM) connection to a server. You can observe and intercept packets as they go through. The Relay is a server+client combo with some special packet handling and forwarding that takes care of the authentication and encryption on the server side. Clients will be asked to login if `offline` is not specified on connection.
```js
const { Relay } = require('bedrock-protocol')
const relay = new Relay({
version: '1.16.220', // The version
/* host and port to listen for clients on */
host: '0.0.0.0',
port: 19132,
/* Where to send upstream packets to */
destination: {
host: '127.0.0.1',
port: 19131
}
})
relay.listen() // Tell the server to start listening.
relay.on('connect', player => {
console.log('New connection', player.connection.address)
// Server is sending a message to the client.
player.on('clientbound', ({ name, params }, des) => {
if (name === 'disconnect') { // Intercept kick
params.message = 'Intercepted' // Change kick message to "Intercepted"
}
})
// Client is sending a message to the server
player.on('serverbound', ({ name, params }, des) => {
if (name === 'text') { // Intercept chat message to server and append time.
params.message += `, on ${new Date().toLocaleString()}`
}
if (name === 'command_request') { // Intercept command request to server and cancel if its "/test"
if (params.command == "/test") {
des.canceled = true
}
}
})
})
```
'Relay' emits 'clientbound' and 'serverbound' events, along with the data for the outgoing packet that can be modified. You can send a packet to the client with `player.queue()` or to the backend server with `player.upstream.queue()`.

3
docs/AUTHORS.md Normal file
View file

@ -0,0 +1,3 @@
mhsjlw <mhsjlw@aol.com> @mhsjlw
Romain Beaumont <romain.rom1@gmail.com> @rom1504
Filiph Sandström <filiph.sandstrom@filfatstudios.com> @filfat

172
docs/CONTRIBUTING.md Normal file
View file

@ -0,0 +1,172 @@
CONTRIBUTING.md
Contributions are always welcome :). If you have any questions, please discuss on the Discord or in a Discussion.
## Updating
Good sources for the Minecraft bedrock protocol are [gophertunnel](https://github.com/Sandertv/gophertunnel/tree/master/minecraft/protocol/packet), [ClouburstMC's protocol library](https://github.com/CloudburstMC/Protocol) and [PocketMine](https://github.com/pmmp/PocketMine-MP/tree/stable/src/pocketmine/network/mcpe/protocol).
Protocol updates need to happen in two places: in minecraft-data to update the protocol schema (the actual data structures for the packets) and here in the protocol library side. If no changes to the underlying protocol are made aside from packet structure changes (add, remove, modify packets) then the only change needed in bedrock-protocol is to update the README documentation and some constants in `src/options.js` (update the CURRENT_VERSION).
Steps to update:
* Update the protocol data in minecraft-data : see the instructions [here](https://github.com/PrismarineJS/minecraft-data/blob/master/doc/bedrock.md).
* Find the relevant changes to the protocol for the current version
* Update the [.YML files](https://github.com/PrismarineJS/minecraft-data/tree/master/data/bedrock/latest) in minecraft-data accordingly (see the [Packet serialization](#Packet_serialization) notes at the bottom here for info on syntax)
* Then follow the steps to build the protocol .YML files into JSON
* Do a release of the minecraft-data package
* Add the version to `src/options.js` here
* Run `npm run build` and `npm test` to test that everything is OK
### Development
For development purposes, you can easily alter the protocol locally without a remote minecraft-data release :
* Run `npm install` on the root of this repo after git cloning
* Open `node_modules/minecraft-data/minecraft-data/data/bedrock/latest/` and update the .YML files as you need, following the schema at the bottom (make sure to update '!version' if you are changing version)
* Go back to the root of this repo and run `npm run build`.
* Then `npm test` ; the protocol changes should be automatically applied
For example, [here](https://github.com/PrismarineJS/minecraft-data/pull/467/files) is a PR for the update to 1.17.30 in minecraft-data - [here](https://github.com/PrismarineJS/bedrock-protocol/pull/150/files) is an accompanying change for bedrock-protocol.
## Code structure
The code structure is similar to node-minecraft-protocol. For raknet, raknet-native is used for Raknet communication.
## Packet serialization
This project uses ProtoDef to serialize and deserialize Minecraft packets. See the documentation [here](https://github.com/ProtoDef-io/node-protodef).
The ProtoDef schema is JSON can be found [here](https://github.com/PrismarineJS/bedrock-protocol/blob/4169453835790de7eeaa8fb6f5a6b4344f71036b/data/1.16.210/protocol.json) for use in other languages.
In bedrock-protocol, JavaScript code is generated from the JSON through the node-protodef compiler.
#### YAML syntax
For easier maintainability, the JSON is generated from a more human readable YAML format. You can read more [here](https://github.com/extremeheat/protodef-yaml).
Some documentation is below.
Packets should go in proto.yml and extra types should go in types.yml.
```yml
# This defines a new data structure, a ProtoDef container.
Position:
# Variable `x` in this struct has a type of `li32`, a little-endian 32-bit integer
x: li32
# `z` is a 32-bit LE *unsigned* integer
z: lu32
# `b` is a 32-bit LE floating point
y: lf32
# Fields starting with `packet_` are structs representing Minecraft packets
packet_player_position:
# Fields starting with ! are ignored by the parser. '!id' is used by the parser when generating the packet map
!id: 0x29 # This packet is ID #0x29
!bound: client # `client` or `server` bound, just for documentation purposes. This has no other effect.
# Read `on_ground` as a boolean
on_ground: bool
# Read `position` as custom data type `Position` defined above.
position: Position
# Reads a 8-bit unsigned integer, then maps it to a string
movement_reason: u8 =>
0: player_jump
1: player_autojump
2: player_sneak
3: player_sprint
4: player_fall
# A `_` as a field name declares an anonymous data structure which will be inlined. Adding a '?' at the end will start a `switch` statement
_: movement_reason ?
# if the condition matches to the string "player_jump" or "player_autojump", there is a data struct that needs to be read
if player_jump or player_autojump:
# read `original_position` as a `Position`
original_position: Position
jump_tick: li64
# if the condition matches "player_fall", read the containing field
if player_fall:
original_position: Position
default: void
# Another way to declare a switch, without an anonymous structure. `player_hunger` will be read as a 8-bit int if movement_reason == "player_sprint"
player_hunger: movement_reason ?
if player_sprint: u8
# The default statement as in a switch statement
default: void
# Square brackets notate an array. At the left is the type of the array values, at the right is the type of
# the length prefix. If no type at the left is specified, the type is defined below.
# Reads an array of `Position`, length-prefixed with a ProtoBuf-type unsigned variable length integer (VarInt)
last_positions: Position[]varint
# Reads an array, length-prefixed with a zigzag-encoded signed VarInt
# The data structure for the array is defined underneath
keys_down: []zigzag32
up: bool
down: bool
shift: bool
```
The above roughly translates to the following JavaScript code to read a packet:
```js
function read_position(stream) {
const ret = {}
ret.x = stream.readLI32()
ret.z = stream.readLU32()
ret.y = stream.readLF32()
return ret
}
function read_player_position(stream) {
const ret = {}
ret.on_ground = Boolean(stream.readU8())
ret.position = read_position(stream)
let __movement_reason = stream.readU8()
let movement_reason = { 0: 'player_jump', 1: 'player_autojump', 2: 'player_sneak', 3: 'player_sprint', 4: 'player_fall' }[__movement_reason]
switch (movement_reason) {
case 'player_jump':
case 'player_autojump':
ret.original_position = read_position(stream)
ret.jump_tick = stream.readLI64()
break
case 'player_fall':
ret.original_position = read_position(stream)
break
default: break
}
ret.player_hunger = undefined
if (movement_reason == 'player_sprint') ret.player_hunger = stream.readU8()
ret.last_positions = []
let __latest_positions_len = stream.readUnsignedVarInt()
for (let i = 0; i < __latest_positions_len; i++) {
ret.last_positions.push(read_player_position(stream))
}
ret.keys_down = []
let __keys_down_len = stream.readZigZagVarInt()
for (let i = 0; i < __keys_down_len; i++) {
const ret1 = {}
ret1.up = Boolean(stream.readU8())
ret1.down = Boolean(stream.readU8())
ret1.shift = Boolean(stream.readU8())
ret.keys_down.push(ret1)
}
return ret
}
```
and the results in the following JSON for the packet:
```json
{
"on_ground": false,
"position": { "x": 0, "y": 2, "z": 0 },
"movement_reason": "player_jump",
"original_position": { "x": 0, "y": 0, "z": 0 },
"jump_tick": 494894984,
"last_positions": [{ "x": 0, "y": 1, "z": 0 }],
"keys_down": []
}
```
Custom ProtoDef types can be inlined as JSON:
```yml
string: ["pstring",{"countType":"varint"}]
```

42
docs/FAQ.md Normal file
View file

@ -0,0 +1,42 @@
## Cant connect to localhost Win10 server with Minecraft Win10 Edition
This issue occurs due to loopback restrictions on Windows 10 UWP apps. To lift this restriction, launch Windows PowerShell as an administrator and run the following:
```ps
CheckNetIsolation LoopbackExempt -a -n="Microsoft.MinecraftUWP_8wekyb3d8bbwe"
```
If you are running a preview or beta release, you can run the following command to unlock that version:
```ps
CheckNetIsolation LoopbackExempt -a -n="Microsoft.MinecraftWindowsBeta"
```
If that still doesn't work, you can inspect what Minecraft versions are available on your system with:
```ps
Get-AppxPackage -AllUsers | Where Name -Match ".*Minecraft.*" | Select Name,InstallLocation,PackageFullName
```
Use the PackageFullName field in place of the `Microsoft.MinecraftUWP_8wekyb3d8bbwe` for the command above.
## Replit
Replit may [not support](https://github.com/PrismarineJS/bedrock-protocol/issues/363) the necessary outbound UDP connections required to connect to a Minecraft server. For further assistance using Replit, please contact Replit support or consider using an alternative hosting service if hosting locally is not possible.
Some alternatives:
* [Gitpod](https://www.gitpod.io/)
* Gitpod is a cloud development environment for teams to efficiently and securely develop software, right from your browser.
* [Github Codespaces](https://github.com/features/codespaces)
* A Codespace is a developer environment like Gitpod that's hosted in the cloud, accessed in your browser.
* [Google Colab](https://colab.research.google.com/)
* Google Colab is a Jupyter notebook environment. Jupyter notebook offer a Python environment where you can write, explain, visualize and execute code straight from a web-based developer environment. For more information on using Colab for JavaScript projects, see [Mineflayer on Google Colab](https://colab.research.google.com/github/PrismarineJS/mineflayer/blob/master/docs/mineflayer.ipynb).
## Kicked during login
NOTE: If you not receiving any errors, the error probably logged in debug mode which is not enabled. To enable it, set `process.env.DEBUG = 'minecraft-protocol'` to the top of the file
Some servers can kick you if you don't set `authTitle` as explained in the README.
## Server clients kicked due to "jwt not active"
The system time is incorrect and needs to be corrected.

Binary file not shown.

View file

@ -1,24 +0,0 @@
var pmp = require('../');
if(process.argv.length !=5) {
console.log("Usage: node client.js <host> <port> <username>");
process.exit(1);
}
var client = pmp.createClient({
host: process.argv[2],
port: parseInt(process.argv[3]),
username:process.argv[4]
});
client.on('mcpe', packet => console.log(packet));
client.on('set_spawn_position', () => {
client.writeMCPE('request_chunk_radius', {
chunk_radius:8
});
});
client.on('error',function(err){
console.log(err);
});

17
examples/client/client.js Normal file
View file

@ -0,0 +1,17 @@
/* eslint-disable */
const bedrock = require('bedrock-protocol')
const client = bedrock.createClient({
host: 'localhost', // optional
port: 19132, // optional, default 19132
username: 'Notch', // the username you want to join as, optional if online mode
offline: false // optional, default false. if true, do not login with Xbox Live. You will not be asked to sign-in if set to true.
})
client.on('text', (packet) => { // Listen for chat messages and echo them back.
if (packet.source_name != client.username) {
client.queue('text', {
type: 'chat', needs_translation: false, source_name: client.username, xuid: '', platform_chat_id: '', filtered_message: '',
message: `${packet.source_name} said: ${packet.message} on ${new Date().toLocaleString()}`
})
}
})

View file

@ -0,0 +1,47 @@
/**
* Do not use this example unless you need to change the login procedure, instead see `client.js`.
*/
process.env.DEBUG = 'minecraft-protocol raknet'
const { Client } = require('bedrock-protocol')
const ChunkColumn = require('bedrock-provider').chunk('bedrock_1.17.10')
async function test () {
const client = new Client({
host: '127.0.0.1',
port: 19132
// You can specify version by adding :
// version: '1.16.210'
})
client.connect()
client.once('resource_packs_info', (packet) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
client.once('resource_pack_stack', (stack) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
})
client.queue('client_cache_status', { enabled: false })
client.queue('request_chunk_radius', { chunk_radius: 1 })
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n })
})
client.on('level_chunk', async packet => {
const cc = new ChunkColumn(packet.x, packet.z)
await cc.networkDecodeNoCache(packet.payload, packet.sub_chunk_count)
const blocks = []
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
blocks.push(cc.getBlock(x, 0, z)) // Read some blocks in this chunk
}
}
})
}
test()

View file

@ -0,0 +1,9 @@
const { createClient } = require('bedrock-protocol')
const client = createClient({ host: '127.0.0.1' })
let ix = 0
client.on('packet', (args) => {
console.log(`Packet ${ix} recieved`)
ix++
})

13
examples/client/realm.js Normal file
View file

@ -0,0 +1,13 @@
/* eslint-disable */
const bedrock = require('bedrock-protocol')
const client = bedrock.createClient({
realms: {
// realmId: '1234567', // Connect the client to a Realm using the Realms ID
// realmInvite: 'https://realms.gg/AB1CD2EFA3B', // Connect the client to a Realm using the Realms invite URL or code
pickRealm: (realms) => realms.find(e => e.name === 'Realm Name') // Connect the client to a Realm using a function that returns a Realm
}
})
client.on('text', (packet) => { // Listen for chat messages
console.log('Received Text:', packet)
})

22
examples/createRelay.js Normal file
View file

@ -0,0 +1,22 @@
const { Relay } = require('bedrock-protocol')
function createRelay () {
console.log('Creating relay')
/* Example to create a non-transparent proxy (or 'Relay') connection to destination server */
const relay = new Relay({
/* host and port for clients to listen to */
host: '0.0.0.0',
port: 19130,
offline: false,
/* Where to send upstream packets to */
destination: {
host: '127.0.0.1',
port: 19132,
offline: false
}
})
relay.conLog = console.debug
relay.listen()
}
createRelay()

View file

@ -1,15 +0,0 @@
var mcpe = require('../');
var Parser = require('protodef').Parser;
var parser = new Parser(mcpe.createProtocol(),'mcpe_packet');
var serializer = mcpe.createSerializer();
parser.write(new Buffer('9F000000010000007E000000804800B0', 'hex'));
parser.on('error', function(err) {
console.log(err.stack);
})
parser.on('data', function(chunk) {
console.log(JSON.stringify(chunk, null, 2));
});

5
examples/ping.js Normal file
View file

@ -0,0 +1,5 @@
const { ping } = require('bedrock-protocol')
ping({ host: 'play.cubecraft.net', port: 19132 }).then(res => {
console.log(res)
})

29
examples/realmRelay.js Normal file
View file

@ -0,0 +1,29 @@
const { Relay } = require('bedrock-protocol')
function createRelay () {
console.log('Creating relay')
/* Example to create a non-transparent proxy (or 'Relay') connection to destination server */
const relay = new Relay({
/* host and port for clients to listen to */
host: '0.0.0.0',
port: 19130,
offline: false,
/* Where to send upstream packets to */
destination: {
realms: {
pickRealm: (realms) => realms.find(e => e.name === 'Realm Name')
},
offline: false
}
})
relay.conLog = console.debug
relay.listen()
relay.on('connect', player => {
// Server is sending a message to the client.
player.on('clientbound', ({ name, params }) => {
if (name === 'text') console.log(params)
})
})
}
createRelay()

37
examples/relay.js Normal file
View file

@ -0,0 +1,37 @@
const { Relay } = require('bedrock-protocol')
// Start your server first on port 19131.
// Start the proxy server
const relay = new Relay({
version: '1.16.220', // The version
/* host and port to listen for clients on */
host: '0.0.0.0',
port: 19132,
/* Where to send upstream packets to */
destination: {
host: '127.0.0.1',
port: 19131
}
})
relay.conLog = console.debug
relay.listen() // Tell the server to start listening.
relay.on('connect', player => {
console.log('New connection', player.connection.address)
// Server is sending a message to the client.
player.on('clientbound', ({ name, params }) => {
if (name === 'disconnect') { // Intercept kick
params.message = 'Intercepted' // Change kick message to "Intercepted"
}
})
// Client is sending a message to the server
player.on('serverbound', ({ name, params }) => {
if (name === 'text') { // Intercept chat message to server and append time.
params.message += `, on ${new Date().toLocaleString()}`
}
})
})
// Now clients can connect to your proxy

View file

@ -1,107 +0,0 @@
'use strict';
var pmp = require('../');
var fs = require("fs");
if(process.argv.length !=4) {
console.log("Usage: node server.js <host> <port>");
process.exit(1);
}
var server = pmp.createServer({
host: process.argv[2],
port: parseInt(process.argv[3]),
name: 'MCPE;Minecraft: PE Server;70 70;0.14.3;0;20'
});
server.on('connection', function(client) {
client.on("mcpe",packet => console.log(packet));
client.on("game_login",packet => {
client.writeMCPE("player_status",{
status:0
});
client.writeMCPE('move_player', {
entity_id: [0,0],
x: 1,
y: 64 + 1.62,
z: 1,
yaw: 0,
head_yaw: 0,
pitch: 0,
mode: 0,
on_ground: 1
});
client.writeMCPE("start_game",{
seed:-1,
dimension:0,
generator:1,
gamemode:1,
entity_id:[0,0],
spawn_x:1,
spawn_y:1,
spawn_z:1,
x:0,
y:1+1.62,
z:0,
unknown1:0,
unknown2:0,
unknown3:0,
unknown4:""
});
client.writeMCPE('set_spawn_position', {
x: 1,
y: 64,
z: 1
});
client.writeMCPE("set_time",{
time:0,
started:1
});
client.writeMCPE('respawn', {
x: 1,
y: 64,
z: 1
});
});
client.on("request_chunk_radius",() => {
client.writeMCPE('chunk_radius_update',{
chunk_radius:1
});
for (let x = -1; x <=1; x++) {
for (let z = -1; z <=1; z++) {
client.writeBatch([{"name":"mcpe","params":{name:"full_chunk_data",params:{
chunk_x: x,
chunk_z: z,
order: 1,
chunk_data:fs.readFileSync(__dirname+"/chunk")
}}}]);
}
}
client.writeMCPE('player_status', {
status: 3
});
client.writeMCPE('set_time', {
time: 0,
started: 1
});
});
client.on('error', function(err) {
console.log(err.stack);
});
client.on('end',function() {
console.log("client left");
})
});

View file

@ -0,0 +1,18 @@
/* eslint-disable */
const bedrock = require('bedrock-protocol')
const server = bedrock.createServer({
host: '0.0.0.0', // optional
port: 19132, // optional
version: '1.19.80', // The server version
motd: { // The message of the day
motd: 'Funtime Server',
levelName: 'Wonderland'
}
})
server.on('connect', client => {
client.on('join', () => { // The client has joined the server.
const date = new Date() // Once client is in the server, send a colorful kick message
client.disconnect(`Good ${date.getHours() < 12 ? '§emorning§r' : '§3afternoon§r'}\n\nMy time is ${date.toLocaleString()} !`)
})
})

150
examples/server/server.js Normal file
View file

@ -0,0 +1,150 @@
/**
* This example spawns a client. For a basic server that disconnects users, see "basicServer.js".
*
* bedrock-protocol server example; to run this example you need to clone this repo from git.
* first need to dump some packets from the vanilla server as there is alot of boilerplate
* to send to clients. The `serverChunks.js` contains the chunk loading code.
*
* In your server implementation, you need to implement each of the following packets to
* get a client to spawn like vanilla. You can look at the dumped packets in `data/1.16.10/sample`
*
* First, dump packets for version 1.16.210 by running `npm run dumpPackets`.
* Then you can run `node server.js <version>` to start this script.
*/
process.env.DEBUG = 'minecraft-protocol' // packet logging
// const fs = require('fs')
const { Server } = require('bedrock-protocol')
const { hasDumps } = require('../../tools/genPacketDumps')
const { waitFor } = require('../../src/datatypes/util')
const { loadWorld } = require('./serverChunks')
const { join } = require('path')
async function startServer (version = '1.17.10', ok) {
if (!hasDumps(version)) {
throw Error('You need to dump some packets first. Run tools/genPacketDumps.js')
}
const Item = require('../../types/Item')(version)
const port = 19132
const server = new Server({ host: '0.0.0.0', port, version })
let loop
const getPath = (packetPath) => join(__dirname, `../data/${server.options.version}/${packetPath}`)
const get = (packetName) => require(getPath(`sample/packets/${packetName}.json`))
server.listen()
console.log('Started server')
// Find the center position from the dumped packets
const respawnPacket = get('respawn')
const world = await loadWorld(version)
const chunks = await world.requestChunks(respawnPacket.x, respawnPacket.z, 2)
// Connect is emitted when a client first joins our server, before authing them
server.on('connect', client => {
// Join is emitted after the client has been authenticated and encryption has started
client.on('join', () => {
console.log('Client joined', client.getUserData())
// ResourcePacksInfo is sent by the server to inform the client on what resource packs the server has. It
// sends a list of the resource packs it has and basic information on them like the version and description.
client.write('resource_packs_info', {
must_accept: false,
has_scripts: false,
behaviour_packs: [],
texture_packs: [],
resource_pack_links: []
})
// ResourcePackStack is sent by the server to send the order in which resource packs and behaviour packs
// should be applied (and downloaded) by the client.
client.write('resource_pack_stack', {
must_accept: false,
behavior_packs: [],
resource_packs: [],
game_version: '',
experiments: [],
experiments_previously_used: false
})
client.once('resource_pack_client_response', async rp => {
// Tell the server we will compress everything (>=1 byte)
client.write('network_settings', { compression_threshold: 1 })
// Send some inventory slots
for (let i = 0; i < 3; i++) {
client.queue('inventory_slot', { window_id: 120, slot: 0, item: new Item().toBedrock() })
}
client.queue('player_list', get('player_list'))
client.queue('start_game', get('start_game'))
client.queue('item_component', { entries: [] })
client.queue('set_spawn_position', get('set_spawn_position'))
client.queue('set_time', { time: 5433771 })
client.queue('set_difficulty', { difficulty: 1 })
client.queue('set_commands_enabled', { enabled: true })
client.queue('adventure_settings', get('adventure_settings'))
client.queue('biome_definition_list', get('biome_definition_list'))
client.queue('available_entity_identifiers', get('available_entity_identifiers'))
client.queue('update_attributes', get('update_attributes'))
client.queue('creative_content', get('creative_content'))
client.queue('inventory_content', get('inventory_content'))
client.queue('player_hotbar', { selected_slot: 3, window_id: 'inventory', select_slot: true })
client.queue('crafting_data', get('crafting_data'))
client.queue('available_commands', get('available_commands'))
client.queue('chunk_radius_update', { chunk_radius: 1 })
client.queue('game_rules_changed', get('game_rules_changed'))
client.queue('respawn', get('respawn'))
for (const chunk of chunks) {
client.queue('level_chunk', chunk)
}
// Uncomment below and comment above to send dumped chunks. We use bedrock-provider in this example which is still a WIP, some blocks may be broken.
// for (const file of fs.readdirSync(`../data/${server.options.version}/sample/chunks`)) {
// const buffer = fs.readFileSync(`../data/${server.options.version}/sample/chunks/` + file)
// // console.log('Sending chunk', buffer)
// client.sendBuffer(buffer)
// }
// Constantly send this packet to the client to tell it the center position for chunks. The client should then request these
// missing chunks from the us if it's missing any within the radius. `radius` is in blocks.
loop = setInterval(() => {
client.write('network_chunk_publisher_update', { coordinates: { x: respawnPacket.x, y: 130, z: respawnPacket.z }, radius: 80 })
}, 4500)
// Wait some time to allow for the client to recieve and load all the chunks
setTimeout(() => {
// Allow the client to spawn
client.write('play_status', { status: 'player_spawn' })
}, 6000)
// Respond to tick synchronization packets
client.on('tick_sync', (packet) => {
client.queue('tick_sync', {
request_time: packet.request_time,
response_time: BigInt(Date.now())
})
})
})
})
})
ok()
return {
kill: () => {
clearInterval(loop)
server.close()
}
}
}
let server
waitFor((res) => {
server = startServer(process.argv[2], res)
}, 1000 * 60 /* Wait 60 seconds for the server to start */, function onTimeout () {
console.error('Server did not start in time')
server?.close()
process.exit(1)
})

View file

@ -0,0 +1,44 @@
// CHUNKS
const { WorldProvider } = require('bedrock-provider')
const { LevelDB } = require('leveldb-zlib')
const { join } = require('path')
async function loadWorld (version) {
const path = join(__dirname, `../../tools/bds-${version}/worlds/Bedrock level/db`)
console.log('Loading world at path', path) // Load world from testing server
const db = new LevelDB(path, { createIfMissing: false })
await db.open()
const wp = new WorldProvider(db, { dimension: 0 })
async function requestChunks (x, z, radius) {
const chunks = []
const cxStart = (x >> 4) - radius
const cxEnd = (x >> 4) + radius
const czStart = (z >> 4) - radius
const czEnd = (z >> 4) + radius
for (let cx = cxStart; cx < cxEnd; cx++) {
for (let cz = czStart; cz < czEnd; cz++) {
const cc = await wp.load(cx, cz, true)
if (!cc) {
continue
}
const cbuf = await cc.networkEncodeNoCache()
chunks.push({
x: cx,
z: cz,
sub_chunk_count: cc.sectionsLen,
cache_enabled: false,
blobs: [],
payload: cbuf
})
}
}
return chunks
}
return { requestChunks }
}
module.exports = { loadWorld }

240
index.d.ts vendored Normal file
View file

@ -0,0 +1,240 @@
import EventEmitter from 'events'
import { Realm } from 'prismarine-realms'
import { ServerDeviceCodeResponse } from 'prismarine-auth'
declare module 'bedrock-protocol' {
type Version = '1.21.93' | '1.21.90' | '1.21.80' | '1.21.70' | '1.21.60' | '1.21.50' | '1.21.42' | '1.21.30' | '1.21.2' | '1.21.0' | '1.20.80' | '1.20.71' | '1.20.61' | '1.20.50' | '1.20.40' | '1.20.30' | '1.20.10' | '1.20.0' | '1.19.80' | '1.19.70' | '1.19.63' | '1.19.62' | '1.19.60' | '1.19.51' | '1.19.50' | '1.19.41' | '1.19.40' | '1.19.31' | '1.19.30' | '1.19.22' | '1.19.21' | '1.19.20' | '1.19.11' | '1.19.10' | '1.19.2' | '1.19.1' | '1.18.31' | '1.18.30' | '1.18.12' | '1.18.11' | '1.18.10' | '1.18.2' | '1.18.1' | '1.18.0' | '1.17.41' | '1.17.40' | '1.17.34' | '1.17.30' | '1.17.11' | '1.17.10' | '1.17.0' | '1.16.220' | '1.16.210' | '1.16.201'
export interface Options {
// The string version to start the client or server as
version?: Version
// For the client, the host of the server to connect to (default: 127.0.0.1)
// For the server, the host to bind to (default: 0.0.0.0)
host: string
// The port to connect or bind to, default: 19132
port: number
// For the client, if we should login with Microsoft/Xbox Live.
// For the server, if we should verify client's authentication with Xbox Live.
offline?: boolean
// Which raknet backend to use
raknetBackend?: 'jsp-raknet' | 'raknet-native' | 'raknet-node'
// If using JS implementation of RakNet, should we use workers? (This only affects the client)
useRaknetWorker?: boolean
// Compression level for zlib, default to 7
compressionLevel?: number
// How frequently the packet queue should be flushed in milliseconds, defaults to 20ms
batchingInterval?: number
}
export interface ClientOptions extends Options {
// The username to connect to the server as
username: string
// The view distance in chunks
viewDistance?: number
// Specifies which game edition to sign in as. Optional, but some servers verify this.
authTitle?: string
// How long to wait in milliseconds while trying to connect to the server.
connectTimeout?: number
// whether to skip initial ping and immediately connect
skipPing?: boolean
// Update the options' port parameter to match the port broadcast on the server's ping data (default to true if `realms` not specified)
followPort?: boolean
// where to log connection information to (default to console.log)
conLog?: any
// used to join a Realm instead of supplying a host/port
realms?: RealmsOptions
// the path to store authentication caches, defaults to .minecraft
profilesFolder?: string | false
// Called when microsoft authorization is needed when not provided it will the information log to the console instead
onMsaCode?: (data: ServerDeviceCodeResponse) => void
}
export interface ServerOptions extends Options {
// The maximum number of players allowed on the server at any time.
maxPlayers?: number
motd?: {
// The header for the MOTD shown in the server list.
motd: string
// The sub-header for the MOTD shown in the server list.
levelName?: string
}
advertisementFn?: () => ServerAdvertisement
}
enum ClientStatus {
Disconnected,
Authenticating,
Initializing,
Initialized
}
export class Connection extends EventEmitter {
readonly status: ClientStatus
// Check if the passed version is less than or greater than the current connected client version.
versionLessThan(version: string | number): boolean
versionGreaterThan(version: string | number): boolean
versionGreaterThanOrEqualTo(version: string | number): boolean
// Writes a Minecraft bedrock packet and sends it without queue batching
write(name: string, params: object): void
// Adds a Minecraft bedrock packet to be sent in the next outgoing batch
queue(name: string, params: object): void
// Writes a MCPE buffer to the connection and skips Protodef serialization. `immediate` if skip queue.
sendBuffer(buffer: Buffer, immediate?: boolean): void
}
type PlayStatus =
| 'login_success'
// # Displays "Could not connect: Outdated client!"
| 'failed_client'
// # Displays "Could not connect: Outdated server!"
| 'failed_spawn'
// # Sent after world data to spawn the player
| 'player_spawn'
// # Displays "Unable to connect to world. Your school does not have access to this server."
| 'failed_invalid_tenant'
// # Displays "The server is not running Minecraft: Education Edition. Failed to connect."
| 'failed_vanilla_edu'
// # Displays "The server is running an incompatible edition of Minecraft. Failed to connect."
| 'failed_edu_vanilla'
// # Displays "Wow this server is popular! Check back later to see if space opens up. Server Full"
| 'failed_server_full'
export class Client extends Connection {
constructor(options: Options)
// The client's EntityID returned by the server
readonly entityId: BigInt
/**
* Close the connection, leave the server.
*/
close(reason?: string): void
/**
* Send a disconnect packet and close the connection
*/
disconnect(): void
}
/**
* `Player` represents a player connected to the server.
*/
export class Player extends Connection {
profile?: {
xuid: string
uuid: string
name: string
}
version: string
getUserData(): object
/**
* Disconnects a client before it has logged in via a PlayStatus packet.
* @param {string} playStatus
*/
sendDisconnectStatus(playStatus: PlayStatus): void
/**
* Disconnects a client
* @param reason The message to be shown to the user on disconnect
* @param hide Don't show the client the reason for the disconnect
*/
disconnect(reason: string, hide?: boolean): void
/**
* Close the connection. Already called by disconnect. Call this to manually close RakNet connection.
*/
close(): void
on(event: 'login', cb: () => void): any
on(event: 'join', cb: () => void): any
on(event: 'close', cb: (reason: string) => void): any
on(event: 'packet', cb: (packet: object) => void): any
on(event: 'spawn', cb: (reason: string) => void): any
}
export class Server extends EventEmitter {
clients: Map<string, Player>
conLog: Function
constructor(options: Options)
listen(): Promise<void>
close(disconnectReason?: string): Promise<void>
on(event: 'connect', cb: (client: Player) => void): any
}
type RelayOptions = Options & {
// Toggle packet logging.
logging?: boolean
// Skip authentication for connecting clients?
offline?: false
// Specifies which game edition to sign in as to the destination server. Optional, but some servers verify this.
authTitle?: string
// Where to proxy requests to.
destination: {
realms?: RealmsOptions
host: string
port: number
// Skip authentication connecting to the remote server?
offline?: boolean
}
// Whether to enable chunk caching (default: false)
enableChunkCaching?: boolean
// Only allow one client to connect at a time (default: false)
forceSingle?: boolean
// Do not disconnect clients on server packet parsing errors and drop the packet instead (default: false)
omitParseErrors?: boolean
// Dispatched when a new client has logged in, and we need authentication
// tokens to join the backend server. Cached after the first login.
// If this is not specified, the client will be disconnected with a login prompt.
onMsaCode?(data: ServerDeviceCodeResponse, client: Client): any
// prismarine-auth configuration
flow?: string,
deviceType?: string
}
export class Relay extends Server {
constructor(options: RelayOptions)
}
export class ServerAdvertisement {
motd: string
name: string
protocol: number
version: string
playersOnline: number
playersMax: number
serverId: string
levelName: string
gamemodeId: number
portV4: number
portV6: number
constructor(obj: object, port: number, version: string)
}
export interface RealmsOptions {
realmId?: string
realmInvite?: string
pickRealm?: (realms: Realm[]) => Realm
}
export function createClient(options: ClientOptions): Client
export function createServer(options: ServerOptions): Server
export function ping({
host,
port
}: {
host: string
port: number
}): Promise<ServerAdvertisement>
}

View file

@ -1 +1,24 @@
module.exports = require('./src/index.js');
if (typeof process !== 'undefined' && parseInt(process.versions.node.split('.')[0]) < 14) {
console.error('Your node version is currently', process.versions.node)
console.error('Please update it to a version >= 14.x.x from https://nodejs.org/')
process.exit(1)
}
const { Client } = require('./src/client')
const { Server } = require('./src/server')
const { Relay } = require('./src/relay')
const { createClient, ping } = require('./src/createClient')
const { createServer } = require('./src/createServer')
const { Titles } = require('prismarine-auth')
const { ServerAdvertisement } = require('./src/server/advertisement')
module.exports = {
Client,
Server,
Relay,
createClient,
ping,
createServer,
title: Titles,
ServerAdvertisement
}

View file

@ -1,34 +1,56 @@
{
"name": "pocket-minecraft-protocol",
"version": "2.2.2",
"description": "Parse and serialize Minecraft PE packets",
"name": "bedrock-protocol",
"version": "3.49.0",
"description": "Minecraft Bedrock Edition protocol library",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build": "cd tools && node compileProtocol.js",
"test": "mocha --retries 2 --bail --exit",
"pretest": "npm run lint",
"lint": "standard",
"vanillaServer": "minecraft-bedrock-server --root tools --version",
"dumpPackets": "node tools/genPacketDumps.js",
"fix": "standard --fix"
},
"keywords": [
"minecraft",
"bedrock",
"pocket-edition",
"protocol"
],
"author": "mhsjlw <mhsjlw@aol.com>",
"contributors": [
"rom1504"
],
"license": "MIT",
"dependencies": {
"prismarine-nbt": "^1.0.0",
"protodef": "^1.2.0",
"raknet": "^1.7.3",
"uuid-1345": "^0.99.6"
"debug": "^4.3.1",
"jsonwebtoken": "^9.0.0",
"jsp-raknet": "^2.1.3",
"minecraft-data": "^3.0.0",
"minecraft-folder-path": "^1.2.0",
"prismarine-auth": "^2.0.0",
"prismarine-nbt": "^2.0.0",
"prismarine-realms": "^1.1.0",
"protodef": "^1.14.0",
"raknet-native": "^1.0.3",
"uuid-1345": "^1.0.2"
},
"optionalDependencies": {
"raknet-node": "^0.5.0"
},
"devDependencies": {
"bedrock-protocol": "file:.",
"bedrock-provider": "^2.0.0",
"leveldb-zlib": "^1.0.1",
"minecraft-bedrock-server": "^1.4.2",
"mocha": "^11.0.1",
"protodef-yaml": "^1.1.0",
"standard": "^17.0.0-2"
},
"devDependencies": {},
"repository": {
"type": "git",
"url": "git+https://github.com/mhsjlw/pocket-minecraft-protocol.git"
"url": "git+https://github.com/PrismarineJS/bedrock-protocol.git"
},
"bugs": {
"url": "https://github.com/mhsjlw/pocket-minecraft-protocol/issues"
"url": "https://github.com/PrismarineJS/bedrock-protocol/issues"
},
"homepage": "https://github.com/mhsjlw/pocket-minecraft-protocol#readme"
"homepage": "https://github.com/PrismarineJS/bedrock-protocol#readme"
}

284
src/client.js Normal file
View file

@ -0,0 +1,284 @@
const { ClientStatus, Connection } = require('./connection')
const { createDeserializer, createSerializer } = require('./transforms/serializer')
const { serialize, isDebug } = require('./datatypes/util')
const debug = require('debug')('minecraft-protocol')
const Options = require('./options')
const auth = require('./client/auth')
const initRaknet = require('./rak')
const { KeyExchange } = require('./handshake/keyExchange')
const Login = require('./handshake/login')
const LoginVerify = require('./handshake/loginVerify')
const debugging = false
class Client extends Connection {
// The RakNet connection
connection
/** @param {{ version: number, host: string, port: number }} options */
constructor (options) {
super()
this.options = { ...Options.defaultOptions, ...options }
this.startGameData = {}
this.clientRuntimeId = null
// Start off without compression on 1.19.30, zlib on below
this.compressionAlgorithm = this.versionGreaterThanOrEqualTo('1.19.30') ? 'none' : 'deflate'
this.compressionThreshold = 512
this.compressionLevel = this.options.compressionLevel
this.batchHeader = 0xfe
if (isDebug) {
this.inLog = (...args) => debug('C ->', ...args)
this.outLog = (...args) => debug('C <-', ...args)
}
this.conLog = this.options.conLog === undefined ? console.log : this.options.conLog
if (!options.delayedInit) {
this.init()
}
}
init () {
this.validateOptions()
this.serializer = createSerializer(this.options.version)
this.deserializer = createDeserializer(this.options.version)
this._loadFeatures()
KeyExchange(this, null, this.options)
Login(this, null, this.options)
LoginVerify(this, null, this.options)
const { RakClient } = initRaknet(this.options.raknetBackend)
const host = this.options.host
const port = this.options.port
this.connection = new RakClient({ useWorkers: this.options.useRaknetWorkers, host, port }, this)
this.emit('connect_allowed')
}
_loadFeatures () {
try {
const mcData = require('minecraft-data')('bedrock_' + this.options.version)
this.features = {
compressorInHeader: mcData.supportFeature('compressorInPacketHeader'),
itemRegistryPacket: mcData.supportFeature('itemRegistryPacket'),
newLoginIdentityFields: mcData.supportFeature('newLoginIdentityFields')
}
} catch (e) {
throw new Error(`Unsupported version: '${this.options.version}', no data available`)
}
}
connect () {
if (!this.connection) throw new Error('Connect not currently allowed') // must wait for `connect_allowed`, or use `createClient`
this.on('session', this._connect)
if (this.options.offline) {
debug('offline mode, not authenticating', this.options)
auth.createOfflineSession(this, this.options)
} else {
auth.authenticate(this, this.options)
}
this.startQueue()
}
validateOptions () {
if (!this.options.host || this.options.port == null) throw Error('Invalid host/port')
Options.validateOptions(this.options)
}
get entityId () {
return this.startGameData.runtime_entity_id
}
onEncapsulated = (encapsulated, inetAddr) => {
const buffer = Buffer.from(encapsulated.buffer)
process.nextTick(() => this.handle(buffer))
}
async ping () {
try {
return await this.connection.ping(this.options.connectTimeout)
} catch (e) {
this.conLog?.(`Unable to connect to [${this.options.host}]/${this.options.port}. Is the server running?`)
throw e
}
}
_connect = async (sessionData) => {
debug('[client] connecting to', this.options.host, this.options.port, sessionData, this.connection)
this.connection.onConnected = () => {
this.status = ClientStatus.Connecting
if (this.versionGreaterThanOrEqualTo('1.19.30')) {
this.queue('request_network_settings', { client_protocol: this.options.protocolVersion })
} else {
this.sendLogin()
}
}
this.connection.onCloseConnection = (reason) => {
if (this.status === ClientStatus.Disconnected) this.conLog?.(`Server closed connection: ${reason}`)
this.close()
}
this.connection.onEncapsulated = this.onEncapsulated
this.connection.connect()
this.connectTimeout = setTimeout(() => {
if (this.status === ClientStatus.Disconnected) {
this.connection.close()
this.emit('error', Error('Connect timed out'))
}
}, this.options.connectTimeout || 9000)
}
updateCompressorSettings (packet) {
this.compressionAlgorithm = packet.compression_algorithm || 'deflate'
this.compressionThreshold = packet.compression_threshold
this.compressionReady = true
}
sendLogin () {
this.status = ClientStatus.Authenticating
this.createClientChain(null, this.options.offline)
const chain = [
this.clientIdentityChain, // JWT we generated for auth
...this.accessToken // Mojang + Xbox JWT from auth
]
let encodedChain
if (this.features.newLoginIdentityFields) { // 1.21.90+
encodedChain = JSON.stringify({
Certificate: JSON.stringify({ chain }),
// 0 = normal, 1 = ss, 2 = offline
AuthenticationType: this.options.offline ? 2 : 0,
Token: ''
})
} else {
encodedChain = JSON.stringify({ chain })
}
debug('Auth chain', encodedChain)
this.write('login', {
protocol_version: this.options.protocolVersion,
tokens: {
identity: encodedChain,
client: this.clientUserChain
}
})
this.emit('loggingIn')
}
onDisconnectRequest (packet) {
this.conLog?.(`Server requested ${packet.hide_disconnect_reason ? 'silent disconnect' : 'disconnect'}: ${packet.message}`)
this.emit('kick', packet)
this.close()
}
onPlayStatus (statusPacket) {
if (this.status === ClientStatus.Initializing && this.options.autoInitPlayer === true) {
if (statusPacket.status === 'player_spawn') {
this.status = ClientStatus.Initialized
if (!this.entityId) {
// We need to wait for start_game in the rare event we get a player_spawn before start_game race condition
this.on('start_game', () => this.write('set_local_player_as_initialized', { runtime_entity_id: this.entityId }))
} else {
this.write('set_local_player_as_initialized', { runtime_entity_id: this.entityId })
}
this.emit('spawn')
}
}
}
disconnect (reason = 'Client leaving', hide = false) {
if (this.status === ClientStatus.Disconnected) return
this.write('disconnect', {
hide_disconnect_screen: hide,
message: reason,
filtered_message: ''
})
this.close(reason)
}
close () {
if (this.status !== ClientStatus.Disconnected) {
this.emit('close') // Emit close once
debug('Client closed!')
}
clearInterval(this.loop)
clearTimeout(this.connectTimeout)
this.q = []
this.q2 = []
this.connection?.close()
this.removeAllListeners()
this.status = ClientStatus.Disconnected
}
readPacket (packet) {
try {
var des = this.deserializer.parsePacketBuffer(packet) // eslint-disable-line
} catch (e) {
// Dump information about the packet only if user is not handling error event.
if (this.listenerCount('error') === 0) this.deserializer.dumpFailedBuffer(packet)
this.emit('error', e)
return
}
const pakData = { name: des.data.name, params: des.data.params }
this.inLog?.('-> C', pakData.name, this.options.logging ? serialize(pakData.params) : '')
this.emit('packet', des)
if (debugging) {
// Packet verifying (decode + re-encode + match test)
if (pakData.name) {
this.deserializer.verify(packet, this.serializer)
}
}
// Abstract some boilerplate before sending to listeners
switch (des.data.name) {
case 'server_to_client_handshake':
this.emit('client.server_handshake', des.data.params)
break
case 'network_settings':
this.updateCompressorSettings(des.data.params)
if (this.status === ClientStatus.Connecting) {
this.sendLogin()
}
break
case 'disconnect': // Client kicked
this.emit(des.data.name, des.data.params) // Emit before we kill all listeners.
this.onDisconnectRequest(des.data.params)
break
case 'start_game':
this.startGameData = pakData.params
// fallsthrough
case 'item_registry': // 1.21.60+ send itemstates in item_registry packet
pakData.params.itemstates?.forEach(state => {
if (state.name === 'minecraft:shield') {
this.serializer.proto.setVariable('ShieldItemID', state.runtime_id)
this.deserializer.proto.setVariable('ShieldItemID', state.runtime_id)
}
})
break
case 'play_status':
if (this.status === ClientStatus.Authenticating) {
this.inLog?.('Server wants to skip encryption')
this.emit('join')
this.status = ClientStatus.Initializing
}
this.onPlayStatus(pakData.params)
break
default:
if (this.status !== ClientStatus.Initializing && this.status !== ClientStatus.Initialized) {
this.inLog?.(`Can't accept ${des.data.name}, client not yet authenticated : ${this.status}`)
return
}
}
// Emit packet
this.emit(des.data.name, des.data.params)
}
}
module.exports = { Client }

119
src/client/auth.js Normal file
View file

@ -0,0 +1,119 @@
const path = require('path')
const { Authflow: PrismarineAuth, Titles } = require('prismarine-auth')
const minecraftFolderPath = require('minecraft-folder-path')
const debug = require('debug')('minecraft-protocol')
const { uuidFrom } = require('../datatypes/util')
const { RealmAPI } = require('prismarine-realms')
function validateOptions (options) {
if (!options.profilesFolder) {
options.profilesFolder = path.join(minecraftFolderPath, 'nmp-cache')
}
if (options.authTitle === undefined) {
options.authTitle = Titles.MinecraftNintendoSwitch
options.deviceType = 'Nintendo'
options.flow = 'live'
}
}
async function realmAuthenticate (options) {
validateOptions(options)
options.authflow = new PrismarineAuth(options.username, options.profilesFolder, options, options.onMsaCode)
const api = RealmAPI.from(options.authflow, 'bedrock')
const getRealms = async () => {
const realms = await api.getRealms()
debug('realms', realms)
if (!realms.length) throw Error('Couldn\'t find any Realms for the authenticated account')
return realms
}
let realm
if (options.realms.realmId) {
const realms = await getRealms()
realm = realms.find(e => e.id === Number(options.realms.realmId))
} else if (options.realms.realmInvite) {
realm = await api.getRealmFromInvite(options.realms.realmInvite)
} else if (options.realms.pickRealm) {
if (typeof options.realms.pickRealm !== 'function') throw Error('realms.pickRealm must be a function')
const realms = await getRealms()
realm = await options.realms.pickRealm(realms)
}
if (!realm) throw Error('Couldn\'t find a Realm to connect to. Authenticated account must be the owner or has been invited to the Realm.')
const { host, port } = await realm.getAddress()
debug('realms connection', { host, port })
options.host = host
options.port = port
}
/**
* Authenticates to Minecraft via device code based Microsoft auth,
* then connects to the specified server in Client Options
*
* @function
* @param {object} client - The client passed to protocol
* @param {object} options - Client Options
*/
async function authenticate (client, options) {
validateOptions(options)
try {
const authflow = options.authflow || new PrismarineAuth(options.username, options.profilesFolder, options, options.onMsaCode)
const chains = await authflow.getMinecraftBedrockToken(client.clientX509).catch(e => {
if (options.password) console.warn('Sign in failed, try removing the password field')
throw e
})
debug('chains', chains)
// First chain is Mojang stuff, second is Xbox profile data used by mc
const jwt = chains[1]
const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) // eslint-disable-line
const xboxProfile = JSON.parse(String(payload))
debug('got xbox profile', xboxProfile)
const profile = {
name: xboxProfile?.extraData?.displayName || 'Player',
uuid: xboxProfile?.extraData?.identity || 'adfcf5ca-206c-404a-aec4-f59fff264c9b', // random
xuid: xboxProfile?.extraData?.XUID || 0
}
return postAuthenticate(client, profile, chains)
} catch (err) {
console.error(err)
client.emit('error', err)
}
}
/**
* Creates an offline session for the client
*/
function createOfflineSession (client, options) {
if (!options.username) throw Error('Must specify a valid username')
const profile = {
name: options.username,
uuid: uuidFrom(options.username), // random
xuid: 0
}
return postAuthenticate(client, profile, []) // No extra JWTs, only send 1 client signed chain with all the data
}
function postAuthenticate (client, profile, chains) {
client.profile = profile
client.username = profile.name
client.accessToken = chains
client.emit('session', profile)
}
module.exports = {
createOfflineSession,
authenticate,
realmAuthenticate
}

195
src/connection.js Normal file
View file

@ -0,0 +1,195 @@
const cipher = require('./transforms/encryption')
const { EventEmitter } = require('events')
const { Versions } = require('./options')
const debug = require('debug')('minecraft-protocol')
const { Framer } = require('./transforms/framer')
const ClientStatus = {
Disconnected: 0,
Connecting: 1,
Authenticating: 2, // Handshaking
Initializing: 3, // Authed, need to spawn
Initialized: 4 // play_status spawn sent by server, client responded with SetPlayerInit packet
}
class Connection extends EventEmitter {
#status = ClientStatus.Disconnected
sendQ = []
sendIds = []
get status () {
return this.#status
}
set status (val) {
debug('* new status', val)
this.emit('status', val)
this.#status = val
}
versionLessThan (version) {
if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version)
return this.options.protocolVersion < (typeof version === 'string' ? Versions[version] : version)
}
versionGreaterThan (version) {
if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version)
return this.options.protocolVersion > (typeof version === 'string' ? Versions[version] : version)
}
versionGreaterThanOrEqualTo (version) {
if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version)
return this.options.protocolVersion >= (typeof version === 'string' ? Versions[version] : version)
}
versionLessThanOrEqualTo (version) {
if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version)
return this.options.protocolVersion <= (typeof version === 'string' ? Versions[version] : version)
}
startEncryption (iv) {
this.encryptionEnabled = true
this.inLog?.('Started encryption', this.sharedSecret, iv)
this.decrypt = cipher.createDecryptor(this, iv)
this.encrypt = cipher.createEncryptor(this, iv)
}
updateItemPalette (palette) {
// In the future, we can send down the whole item palette if we need
// but since it's only one item, we can just make a single variable.
let shieldItemID
for (const state of palette) {
if (state.name === 'minecraft:shield') {
shieldItemID = state.runtime_id
break
}
}
if (shieldItemID) {
this.serializer.proto.setVariable('ShieldItemID', shieldItemID)
this.deserializer.proto.setVariable('ShieldItemID', shieldItemID)
}
}
_processOutbound (name, params) {
if (name === 'item_registry') {
this.updateItemPalette(params.itemstates)
} else if (name === 'start_game' && params.itemstates) {
this.updateItemPalette(params.itemstates)
}
}
write (name, params) {
this.outLog?.(name, params)
this._processOutbound(name, params)
const batch = new Framer(this)
const packet = this.serializer.createPacketBuffer({ name, params })
batch.addEncodedPacket(packet)
if (this.encryptionEnabled) {
this.sendEncryptedBatch(batch)
} else {
this.sendDecryptedBatch(batch)
}
}
queue (name, params) {
this.outLog?.('Q <- ', name, params)
this._processOutbound(name, params)
const packet = this.serializer.createPacketBuffer({ name, params })
if (name === 'level_chunk') {
// Skip queue, send ASAP
this.sendBuffer(packet)
return
}
this.sendQ.push(packet)
this.sendIds.push(name)
}
_tick () {
if (this.sendQ.length) {
const batch = new Framer(this)
batch.addEncodedPackets(this.sendQ)
this.sendQ = []
this.sendIds = []
if (this.encryptionEnabled) {
this.sendEncryptedBatch(batch)
} else {
this.sendDecryptedBatch(batch)
}
}
}
onTick = this._tick.bind(this)
startQueue () {
this.sendQ = []
this.loop = setInterval(this.onTick, this.options.batchingInterval || 20)
}
/**
* Sends a MCPE packet buffer
*/
sendBuffer (buffer, immediate = false) {
if (immediate) {
const batch = new Framer(this)
batch.addEncodedPacket(buffer)
if (this.encryptionEnabled) {
this.sendEncryptedBatch(batch)
} else {
this.sendDecryptedBatch(batch)
}
} else {
this.sendQ.push(buffer)
this.sendIds.push('rawBuffer')
}
}
sendDecryptedBatch (batch) {
// send to raknet
this.sendMCPE(batch.encode(), true)
}
sendEncryptedBatch (batch) {
const buf = batch.getBuffer()
this.encrypt(buf)
}
sendMCPE (buffer, immediate) {
if (this.connection.connected === false || this.status === ClientStatus.Disconnected) return
try {
this.connection.sendReliable(buffer, immediate)
} catch (e) {
debug('while sending to', this.connection, e)
}
}
// These are callbacks called from encryption.js
onEncryptedPacket = (buf) => {
const packet = this.batchHeader ? Buffer.concat([Buffer.from([this.batchHeader]), buf]) : buf
this.sendMCPE(packet)
}
onDecryptedPacket = (buf) => {
const packets = Framer.getPackets(buf)
for (const packet of packets) {
this.readPacket(packet)
}
}
handle (buffer) { // handle encapsulated
if (!this.batchHeader || buffer[0] === this.batchHeader) { // wrapper
if (this.encryptionEnabled) {
this.decrypt(buffer.slice(1))
} else {
const packets = Framer.decode(this, buffer)
for (const packet of packets) {
this.readPacket(packet)
}
}
} else {
throw Error('Bad packet header ' + buffer[0])
}
}
}
module.exports = { ClientStatus, Connection }

View file

@ -1,61 +1,101 @@
'use strict';
const assert = require('assert');
const raknet = require('raknet');
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const ProtoDef = require('protodef').ProtoDef;
const batchProto=new ProtoDef();
batchProto.addTypes(require("./datatypes/minecraft"));
batchProto.addType("insideBatch",["endOfArray",{"type":["buffer",{"countType":"i32"}]}]);
const { Client } = require('./client')
const { RakClient } = require('./rak')('raknet-native')
const { sleep } = require('./datatypes/util')
const assert = require('assert')
const Options = require('./options')
const advertisement = require('./server/advertisement')
const auth = require('./client/auth')
function createClient(options) {
assert.ok(options, 'options is required');
var port = options.port || 19132;
var host = options.host || 'localhost';
/** @param {{ version?: number, host: string, port?: number, connectTimeout?: number, skipPing?: boolean }} options */
function createClient (options) {
assert(options)
const client = new Client({ port: 19132, followPort: !options.realms, ...options, delayedInit: true })
assert.ok(options.username, 'username is required');
function onServerInfo () {
client.on('connect_allowed', () => connect(client))
if (options.skipPing) {
client.init()
} else {
ping(client.options).then(ad => {
const adVersion = ad.version?.split('.').slice(0, 3).join('.') // Only 3 version units
client.options.version = options.version ?? (Options.Versions[adVersion] ? adVersion : Options.CURRENT_VERSION)
options.customPackets=require('../data/protocol');
options.customTypes=require('./datatypes/minecraft');
var client=raknet.createClient(options);
client.username = options.username;
client.on('mcpe',packet => client.emit(packet.name,packet.params))
client.writeMCPE=(name,packet) => {
client.writeEncapsulated('mcpe',{
name:name,
params:packet
});
};
client.on('login', function() {
client.writeMCPE('game_login',
{
username: client.username,
protocol: 70,
protocol2: 70,
client_id: [ -1, -697896776 ],
client_uuid: '86372ed8-d055-b23a-9171-5e3ac594d766',
server_address: client.host+":"+client.port,
client_secret: new Buffer('e8 88 db 7b 9f f2 f0 44 a3 51 08 18 4e 8c 7f 9a'.replace(/ /g,''),'hex'),
skin:
{
skinType: 'Standard_Steve',
texture: fs.readFileSync(path.join(__dirname,'texture'))
if (ad.portV4 && client.options.followPort) {
client.options.port = ad.portV4
}
}
);
});
client.conLog?.(`Connecting to ${client.options.host}:${client.options.port} ${ad.motd} (${ad.levelName}), version ${ad.version} ${client.options.version !== ad.version ? ` (as ${client.options.version})` : ''}`)
client.init()
}).catch(e => client.emit('error', e))
}
}
client.on('batch', function(packet) {
var buf = zlib.inflateSync(packet.payload);
var packets=batchProto.parsePacketBuffer("insideBatch",buf).data;
packets.forEach(packet => client.readEncapsulatedPacket(Buffer.concat([new Buffer([0x8e]),packet])));
});
return client;
if (options.realms) {
auth.realmAuthenticate(client.options).then(onServerInfo).catch(e => client.emit('error', e))
} else {
onServerInfo()
}
return client
}
module.exports = createClient;
function connect (client) {
// Actually connect
client.connect()
client.once('resource_packs_info', (packet) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
client.once('resource_pack_stack', (stack) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
})
client.queue('client_cache_status', { enabled: false })
if (client.versionLessThanOrEqualTo('1.20.80')) client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n })
sleep(500).then(() => client.queue('request_chunk_radius', { chunk_radius: client.viewDistance || 10 }))
})
if (client.versionLessThanOrEqualTo('1.20.80')) {
const keepAliveInterval = 10
const keepAliveIntervalBig = BigInt(keepAliveInterval)
let keepalive
client.tick = 0n
client.once('spawn', () => {
keepalive = setInterval(() => {
// Client fills out the request_time and the server does response_time in its reply.
client.queue('tick_sync', { request_time: client.tick, response_time: 0n })
client.tick += keepAliveIntervalBig
}, 50 * keepAliveInterval)
client.on('tick_sync', async packet => {
client.emit('heartbeat', packet.response_time)
client.tick = packet.response_time
})
})
client.once('close', () => {
clearInterval(keepalive)
})
}
}
async function ping ({ host, port }) {
const con = new RakClient({ host, port })
try {
return advertisement.fromServerName(await con.ping())
} finally {
con.close()
}
}
module.exports = { createClient, ping }

View file

@ -1,47 +1,10 @@
const raknet = require('raknet');
const zlib = require('zlib');
const ProtoDef = require('protodef').ProtoDef;
const batchProto=new ProtoDef();
batchProto.addTypes(require("./datatypes/minecraft"));
batchProto.addType("insideBatch",["endOfArray",{"type":["buffer",{"countType":"i32"}]}]);
const { Server } = require('./server')
function createServer(options) {
options = options || {};
var port = options.port != null ?
options.port :
options['server-port'] != null ?
options['server-port'] :
19132;
var host = options.host || '0.0.0.0';
options.customPackets=require("../data/protocol");
options.customTypes=require("./datatypes/minecraft");
var server = raknet.createServer(options);
server.name = options.name || "Minecraft Server";
server.motd = options.motd || "A Minecraft server";
server.maxPlayers = options['max-players'] || 20;
server.playerCount = 0;
server.on("connection", function (client) {
client.on("mcpe",packet => client.emit(packet.name,packet.params));
client.writeMCPE=(name,packet) => {
client.writeEncapsulated("mcpe",{
name:name,
params:packet
});
};
client.writeBatch=function(packets) {
const payload=zlib.deflateSync(batchProto.createPacketBuffer("insideBatch",
packets.map(packet =>
client.encapsulatedPacketSerializer.createPacketBuffer(packet).slice(1))));
client.writeMCPE("batch",{
payload:payload
});
}
});
return server;
function createServer (options) {
if (!options.port) options.port = 19132
const server = new Server(options)
server.listen()
return server
}
module.exports = createServer;
module.exports = { createServer }

View file

@ -0,0 +1,155 @@
/* eslint-disable */
const UUID = require('uuid-1345')
const minecraft = require('./minecraft')
const [Read, Write, 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
}]
/**
* Encapsulated data with length prefix
*/
Read.encapsulated = ['parametrizable', (compiler, { lengthType, type }) => {
return compiler.wrapCode(`
const payloadSize = ${compiler.callType(lengthType, 'offset')}
const { value, size } = ctx.${type}(buffer, offset + payloadSize.size)
return { value, size: size + payloadSize.size }
`.trim())
}]
Write.encapsulated = ['parametrizable', (compiler, { lengthType, type }) => {
return compiler.wrapCode(`
const buf = Buffer.allocUnsafe(buffer.length - offset)
const payloadSize = (ctx.${type})(value, buf, 0)
let size = (ctx.${lengthType})(payloadSize, buffer, offset)
size += buf.copy(buffer, size, 0, payloadSize)
return size
`.trim())
}]
SizeOf.encapsulated = ['parametrizable', (compiler, { lengthType, type }) => {
return compiler.wrapCode(`
const payloadSize = (ctx.${type})(value)
return (ctx.${lengthType})(payloadSize) + payloadSize
`.trim())
}]
/**
* Read NBT until end of buffer or \0
*/
Read.nbtLoop = ['context', (buffer, offset) => {
const values = []
while (buffer[offset] != 0) {
const n = ctx.nbt(buffer, offset)
values.push(n.value)
offset += n.size
}
return { value: values, size: buffer.length - offset }
}]
Write.nbtLoop = ['context', (value, buffer, offset) => {
for (const val of value) {
offset = ctx.nbt(val, buffer, offset)
}
buffer.writeUint8(0, offset)
return offset + 1
}]
SizeOf.nbtLoop = ['context', (value, buffer, offset) => {
let size = 1
for (const val of value) {
size += ctx.nbt(val, buffer, offset)
}
return size
}]
/**
* Read rotation float encoded as a byte
*/
Read.byterot = ['context', (buffer, offset) => {
const val = buffer.readUint8(offset)
return { value: (val * (360 / 256)), size: 1 }
}]
Write.byterot = ['context', (value, buffer, offset) => {
const val = (value / (360 / 256))
buffer.writeUint8(val, offset)
return offset + 1
}]
SizeOf.byterot = ['context', (value, buffer, offset) => {
return 1
}]
/**
* NBT
*/
Read.nbt = ['native', minecraft.nbt[0]]
Write.nbt = ['native', minecraft.nbt[1]]
SizeOf.nbt = ['native', minecraft.nbt[2]]
Read.lnbt = ['native', minecraft.lnbt[0]]
Write.lnbt = ['native', minecraft.lnbt[1]]
SizeOf.lnbt = ['native', minecraft.lnbt[2]]
/**
* Command Packet
* - used for determining the size of the following enum
*/
Read.enum_size_based_on_values_len = ['parametrizable', (compiler) => {
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) => {
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) => {
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 }

View file

@ -1,135 +1,160 @@
'use strict';
const nbt = require('prismarine-nbt')
const UUID = require('uuid-1345')
var nbt = require('prismarine-nbt');
const UUID = require('uuid-1345');
const protoLE = nbt.protos.little
const protoLEV = nbt.protos.littleVarint
function readUUID(buffer, offset) {
if(offset+16>buffer.length)
throw new PartialReadError();
function readUUID (buffer, offset) {
if (offset + 16 > buffer.length) { throw new Error('Reached end of buffer') }
return {
value: UUID.stringify(buffer.slice(offset,16+offset)),
value: UUID.stringify(buffer.slice(offset, 16 + offset)),
size: 16
};
}
}
function writeUUID(value, buffer, offset) {
const buf=UUID.parse(value);
buf.copy(buffer,offset);
return offset + 16;
function writeUUID (value, buffer, offset) {
const buf = UUID.parse(value)
buf.copy(buffer, offset)
return offset + 16
}
function readNbt(buffer, offset) {
return nbt.protoLE.read(buffer,offset,"nbt");
// Little Endian + varints
function readNbt (buffer, offset) {
return protoLEV.read(buffer, offset, 'nbt')
}
function writeNbt(value, buffer, offset) {
return nbt.protoLE.write(value,buffer,offset,"nbt");
function writeNbt (value, buffer, offset) {
return protoLEV.write(value, buffer, offset, 'nbt')
}
function sizeOfNbt(value) {
return nbt.protoLE.sizeOf(value,"nbt");
function sizeOfNbt (value) {
return protoLEV.sizeOf(value, 'nbt')
}
function readEntityMetadata(buffer, offset, _ref) {
var type = _ref.type;
var endVal = _ref.endVal;
// Little Endian
var cursor = offset;
var metadata = [];
var item = undefined;
function readNbtLE (buffer, offset) {
const r = protoLE.read(buffer, offset, 'nbt')
// End size is 3 for some reason
if (r.value.type === 'end') return { value: r.value, size: 1 }
return r
}
function writeNbtLE (value, buffer, offset) {
if (value.type === 'end') {
buffer.writeInt8(0, offset)
return offset + 1
}
return protoLE.write(value, buffer, offset, 'nbt')
}
function sizeOfNbtLE (value) {
if (value.type === 'end') return 1
return protoLE.sizeOf(value, 'nbt')
}
function readEntityMetadata (buffer, offset, _ref) {
const type = _ref.type
const endVal = _ref.endVal
let cursor = offset
const metadata = []
let item
while (true) {
if (offset + 1 > buffer.length) throw new PartialReadError();
item = buffer.readUInt8(cursor);
if (offset + 1 > buffer.length) throw new Error('Reached end of buffer')
item = buffer.readUInt8(cursor)
if (item === endVal) {
return {
value: metadata,
size: cursor + 1 - offset
};
}
}
var results = this.read(buffer, cursor, type, {});
metadata.push(results.value);
cursor += results.size;
const results = this.read(buffer, cursor, type, {})
metadata.push(results.value)
cursor += results.size
}
}
function writeEntityMetadata(value, buffer, offset, _ref2) {
var type = _ref2.type;
var endVal = _ref2.endVal;
function writeEntityMetadata (value, buffer, offset, _ref2) {
const type = _ref2.type
const endVal = _ref2.endVal
var self = this;
const self = this
value.forEach(function (item) {
offset = self.write(item, buffer, offset, type, {});
});
buffer.writeUInt8(endVal, offset);
return offset + 1;
offset = self.write(item, buffer, offset, type, {})
})
buffer.writeUInt8(endVal, offset)
return offset + 1
}
function sizeOfEntityMetadata(value, _ref3) {
var type = _ref3.type;
function sizeOfEntityMetadata (value, _ref3) {
const type = _ref3.type
var size = 1;
for (var i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {});
let size = 1
for (let i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {})
}
return size;
return size
}
function readIpAddress(buffer, offset) {
var address = buffer[offset] + '.' + buffer[offset+1] + '.' + buffer[offset+2] + '.' + buffer[offset+3];
function readIpAddress (buffer, offset) {
const address = buffer[offset] + '.' + buffer[offset + 1] + '.' + buffer[offset + 2] + '.' + buffer[offset + 3]
return {
size: 4,
value: address
}
}
function writeIpAddress(value, buffer, offset) {
var address = value.split('.');
function writeIpAddress (value, buffer, offset) {
const address = value.split('.')
address.forEach(function(b) {
buffer[offset] = parseInt(b);
offset++;
});
address.forEach(function (b) {
buffer[offset] = parseInt(b)
offset++
})
return offset;
return offset
}
function readEndOfArray(buffer, offset, typeArgs) {
var type=typeArgs.type;
var cursor = offset;
var elements = [];
while(cursor<buffer.length) {
var results = this.read(buffer, cursor, type, {});
elements.push(results.value);
cursor += results.size;
function readEndOfArray (buffer, offset, typeArgs) {
const type = typeArgs.type
let cursor = offset
const elements = []
while (cursor < buffer.length) {
const results = this.read(buffer, cursor, type, {})
elements.push(results.value)
cursor += results.size
}
return {
value: elements,
size: cursor - offset
};
}
function writeEndOfArray(value, buffer, offset,typeArgs) {
var type=typeArgs.type;
var self = this;
value.forEach(function(item) {
offset = self.write(item, buffer, offset, type, {});
});
return offset;
}
function sizeOfEndOfArray(value, typeArgs) {
var type=typeArgs.type;
var size = 0;
for(var i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {});
}
return size;
}
function writeEndOfArray (value, buffer, offset, typeArgs) {
const type = typeArgs.type
const self = this
value.forEach(function (item) {
offset = self.write(item, buffer, offset, type, {})
})
return offset
}
function sizeOfEndOfArray (value, typeArgs) {
const type = typeArgs.type
let size = 0
for (let i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {})
}
return size
}
module.exports = {
'uuid': [readUUID, writeUUID, 16],
'nbt': [readNbt, writeNbt, sizeOfNbt],
'entityMetadataLoop': [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
'ipAddress': [readIpAddress, writeIpAddress, 4],
'endOfArray':[readEndOfArray,writeEndOfArray,sizeOfEndOfArray]
};
uuid: [readUUID, writeUUID, 16],
nbt: [readNbt, writeNbt, sizeOfNbt],
lnbt: [readNbtLE, writeNbtLE, sizeOfNbtLE],
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
ipAddress: [readIpAddress, writeIpAddress, 4],
endOfArray: [readEndOfArray, writeEndOfArray, sizeOfEndOfArray]
}

48
src/datatypes/util.js Normal file
View file

@ -0,0 +1,48 @@
const fs = require('fs')
const UUID = require('uuid-1345')
function getFiles (dir) {
let results = []
const list = fs.readdirSync(dir)
list.forEach((file) => {
file = dir + '/' + file
const stat = fs.statSync(file)
if (stat && stat.isDirectory()) {
results = results.concat(getFiles(file))
} else {
results.push(file)
}
})
return results
}
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function waitFor (cb, withTimeout, onTimeout) {
let t
const ret = await Promise.race([
new Promise((resolve, reject) => cb(resolve, reject)),
new Promise(resolve => { t = setTimeout(() => resolve('timeout'), withTimeout) })
])
clearTimeout(t)
if (ret === 'timeout') await onTimeout()
return ret
}
function serialize (obj = {}, fmt) {
return JSON.stringify(obj, (k, v) => typeof v === 'bigint' ? v.toString() : v, fmt)
}
function uuidFrom (string) {
return UUID.v3({ namespace: '6ba7b811-9dad-11d1-80b4-00c04fd430c8', name: string })
}
function nextUUID () {
return uuidFrom(Date.now().toString())
}
const isDebug = process.env.DEBUG?.includes('minecraft-protocol')
module.exports = { getFiles, sleep, waitFor, serialize, uuidFrom, nextUUID, isDebug }

View file

@ -0,0 +1,3 @@
module.exports = {
PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp'
}

View file

@ -0,0 +1,93 @@
const { ClientStatus } = require('../connection')
const JWT = require('jsonwebtoken')
const crypto = require('crypto')
const debug = require('debug')('minecraft-protocol')
const SALT = '🧂'
const curve = 'secp384r1'
const pem = { format: 'pem', type: 'sec1' }
const der = { format: 'der', type: 'spki' }
function KeyExchange (client, server, options) {
// Generate a key pair at program start up
client.ecdhKeyPair = crypto.generateKeyPairSync('ec', { namedCurve: curve })
client.publicKeyDER = client.ecdhKeyPair.publicKey.export(der)
client.privateKeyPEM = client.ecdhKeyPair.privateKey.export(pem)
client.clientX509 = client.publicKeyDER.toString('base64')
function startClientboundEncryption (publicKey) {
debug('[encrypt] Client pub key base64: ', publicKey)
const pubKeyDer = crypto.createPublicKey({ key: Buffer.from(publicKey.key, 'base64'), ...der })
// Shared secret from the client's public key + our private key
client.sharedSecret = crypto.diffieHellman({ privateKey: client.ecdhKeyPair.privateKey, publicKey: pubKeyDer })
// Secret hash we use for packet encryption:
// From the public key of the remote and the private key
// of the local, a shared secret is generated using ECDH.
// The secret key bytes are then computed as
// sha256(server_token + shared_secret). These secret key
// bytes are 32 bytes long.
const secretHash = crypto.createHash('sha256')
secretHash.update(SALT)
secretHash.update(client.sharedSecret)
client.secretKeyBytes = secretHash.digest()
const token = JWT.sign({
salt: toBase64(SALT),
signedToken: client.clientX509
}, client.ecdhKeyPair.privateKey, { algorithm: 'ES384', header: { x5u: client.clientX509 } })
client.write('server_to_client_handshake', { token })
// The encryption scheme is AES/CFB8/NoPadding with the
// secret key being the result of the sha256 above and
// the IV being the first 16 bytes of this secret key.
const initial = client.secretKeyBytes.slice(0, 16)
client.startEncryption(initial)
}
function startServerboundEncryption (token) {
debug('[encrypt] Starting serverbound encryption', token)
const jwt = token?.token
if (!jwt) {
throw Error('Server did not return a valid JWT, cannot start encryption')
}
// No verification here, not needed
const [header, payload] = jwt.split('.').map(k => Buffer.from(k, 'base64'))
const head = JSON.parse(String(header))
const body = JSON.parse(String(payload))
const pubKeyDer = crypto.createPublicKey({ key: Buffer.from(head.x5u, 'base64'), ...der })
// Shared secret from the client's public key + our private key
client.sharedSecret = crypto.diffieHellman({ privateKey: client.ecdhKeyPair.privateKey, publicKey: pubKeyDer })
const salt = Buffer.from(body.salt, 'base64')
const secretHash = crypto.createHash('sha256')
secretHash.update(salt)
secretHash.update(client.sharedSecret)
client.secretKeyBytes = secretHash.digest()
const iv = client.secretKeyBytes.slice(0, 16)
client.startEncryption(iv)
// It works! First encrypted packet :)
client.write('client_to_server_handshake', {})
this.emit('join')
client.status = ClientStatus.Initializing
}
client.on('server.client_handshake', startClientboundEncryption)
client.on('client.server_handshake', startServerboundEncryption)
}
function toBase64 (string) {
return Buffer.from(string).toString('base64')
}
module.exports = { KeyExchange }

80
src/handshake/login.js Normal file
View file

@ -0,0 +1,80 @@
const JWT = require('jsonwebtoken')
const { nextUUID } = require('../datatypes/util')
const { PUBLIC_KEY } = require('./constants')
const algorithm = 'ES384'
module.exports = (client, server, options) => {
const skinData = require('minecraft-data')('bedrock_' + options.version).defaultSkin
client.createClientChain = (mojangKey, offline) => {
const privateKey = client.ecdhKeyPair.privateKey
let token
if (offline) {
const payload = {
extraData: {
displayName: client.username,
identity: client.profile.uuid,
titleId: '89692877',
XUID: '0'
},
certificateAuthority: true,
identityPublicKey: client.clientX509
}
token = JWT.sign(payload, privateKey, { algorithm, notBefore: 0, issuer: 'self', expiresIn: 60 * 60, header: { x5u: client.clientX509, typ: undefined } })
} else {
token = JWT.sign({
identityPublicKey: mojangKey || PUBLIC_KEY,
certificateAuthority: true
}, privateKey, { algorithm, header: { x5u: client.clientX509, typ: undefined } })
}
client.clientIdentityChain = token
client.createClientUserChain(privateKey)
}
client.createClientUserChain = (privateKey) => {
let payload = {
...skinData,
ClientRandomId: Date.now(),
CurrentInputMode: 1,
DefaultInputMode: 1,
DeviceId: nextUUID(),
DeviceModel: 'PrismarineJS',
DeviceOS: client.session?.deviceOS || 7,
GameVersion: options.version || '1.16.201',
GuiScale: -1,
LanguageCode: 'en_GB', // TODO locale
GraphicsMode: 1, // 1:simple, 2:fancy, 3:advanced, 4:ray_traced
PlatformOfflineId: '',
PlatformOnlineId: '', // chat
// PlayFabID is the PlayFab ID produced for the skin. PlayFab is the company that hosts the Marketplace,
// skins and other related features from the game. This ID is the ID of the skin used to store the skin
// inside of PlayFab.The playfab ID is always lowercased.
PlayFabId: nextUUID().replace(/-/g, '').slice(0, 16).toLowerCase(), // 1.16.210
SelfSignedId: nextUUID(),
ServerAddress: `${options.host}:${options.port}`,
ThirdPartyName: client.profile.name, // Gamertag
ThirdPartyNameOnly: client.versionGreaterThanOrEqualTo('1.21.90') ? undefined : false,
UIProfile: 0,
IsEditorMode: false,
TrustedSkin: client.versionGreaterThanOrEqualTo('1.19.20') ? false : undefined,
OverrideSkin: client.versionGreaterThanOrEqualTo('1.19.62') ? false : undefined,
CompatibleWithClientSideChunkGen: client.versionGreaterThanOrEqualTo('1.19.80') ? false : undefined,
MaxViewDistance: client.versionGreaterThanOrEqualTo('1.21.42') ? 0 : undefined,
MemoryTier: client.versionGreaterThanOrEqualTo('1.21.42') ? 0 : undefined,
PlatformType: client.versionGreaterThanOrEqualTo('1.21.42') ? 0 : undefined
}
const customPayload = options.skinData || {}
payload = { ...payload, ...customPayload }
payload.ServerAddress = `${options.host}:${options.port}`
client.clientUserChain = JWT.sign(payload, privateKey, { algorithm, header: { x5u: client.clientX509, typ: undefined }, noTimestamp: true /* pocketmine.. */ })
}
}

View file

@ -0,0 +1,74 @@
const JWT = require('jsonwebtoken')
const constants = require('./constants')
const debug = require('debug')('minecraft-protocol')
const crypto = require('crypto')
module.exports = (client, server, options) => {
// Refer to the docs:
// https://web.archive.org/web/20180917171505if_/https://confluence.yawk.at/display/PEPROTOCOL/Game+Packets#GamePackets-Login
const getDER = b64 => crypto.createPublicKey({ key: Buffer.from(b64, 'base64'), format: 'der', type: 'spki' })
function verifyAuth (chain) {
let data = {}
// There are three JWT tokens sent to us, one signed by the client
// one signed by Mojang with the Mojang token we have and another one
// from Xbox with addition user profile data
// We verify that at least one of the tokens in the chain has been properly
// signed by Mojang by checking the x509 public key in the JWT headers
let didVerify = false
let pubKey = getDER(getX5U(chain[0])) // the first one is client signed, allow it
let finalKey = null
for (const token of chain) {
const decoded = JWT.verify(token, pubKey, { algorithms: ['ES384'] })
// Check if signed by Mojang key
const x5u = getX5U(token)
if (x5u === constants.PUBLIC_KEY && !data.extraData?.XUID) {
didVerify = true
debug('Verified client with mojang key', x5u)
}
pubKey = decoded.identityPublicKey ? getDER(decoded.identityPublicKey) : x5u
finalKey = decoded.identityPublicKey || finalKey // non pem
data = { ...data, ...decoded }
}
if (!didVerify && !options.offline) {
client.disconnect('disconnectionScreen.notAuthenticated')
}
return { key: finalKey, data }
}
function verifySkin (publicKey, token) {
const pubKey = getDER(publicKey)
const decoded = JWT.verify(token, pubKey, { algorithms: ['ES384'] })
return decoded
}
client.decodeLoginJWT = (authTokens, skinTokens) => {
const { key, data } = verifyAuth(authTokens)
const skinData = verifySkin(key, skinTokens)
return { key, userData: data, skinData }
}
client.encodeLoginJWT = (localChain, mojangChain) => {
const chains = []
chains.push(localChain)
for (const chain of mojangChain) {
chains.push(chain)
}
return chains
}
}
function getX5U (token) {
const [header] = token.split('.')
const hdec = Buffer.from(header, 'base64').toString('utf-8')
const hjson = JSON.parse(hdec)
return hjson.x5u
}

View file

@ -1,7 +0,0 @@
module.exports = {
createSerializer: require("./transforms/serializer").createSerializer,
createDeserializer: require("./transforms/serializer").createDeserializer,
createProtocol: require('./transforms/serializer').createProtocol,
createServer: require("./createServer"),
createClient: require("./createClient")
};

52
src/options.js Normal file
View file

@ -0,0 +1,52 @@
const mcData = require('minecraft-data')
// Minimum supported version (< will be kicked)
const MIN_VERSION = '1.16.201'
// Currently supported verson. Note, clients with newer versions can still connect as long as data is in minecraft-data
const CURRENT_VERSION = '1.21.111'
const Versions = Object.fromEntries(mcData.versions.bedrock.filter(e => e.releaseType === 'release').map(e => [e.minecraftVersion, e.version]))
// Skip some low priority versions (middle major) on Github Actions to allow faster CI
const skippedVersionsOnGithubCI = ['1.16.210', '1.17.10', '1.17.30', '1.18.11', '1.19.10', '1.19.20', '1.19.30', '1.19.40', '1.19.50', '1.19.60', '1.19.63', '1.19.70', '1.20.10', '1.20.15', '1.20.30', '1.20.40', '1.20.50', '1.20.61', '1.20.71', '1.21.2', '1.21.20', '1.21.30', '1.21.42', '1.21.50', '1.21.60', '1.21.70', '1.21.80', '1.21.90']
const testedVersions = process.env.CI ? Object.keys(Versions).filter(v => !skippedVersionsOnGithubCI.includes(v)) : Object.keys(Versions)
const defaultOptions = {
// https://minecraft.wiki/w/Protocol_version#Bedrock_Edition_2
version: CURRENT_VERSION,
// client: If we should send SetPlayerInitialized to the server after getting play_status spawn.
// if this is disabled, no 'spawn' event will be emitted, you should manually set
// client.status to ClientStatus.Initialized after sending the init packet.
autoInitPlayer: true,
// If true, do not authenticate with Xbox Live
offline: false,
// Milliseconds to wait before aborting connection attempt
connectTimeout: 9000,
// Specifies the raknet implementation to use
raknetBackend: 'raknet-native',
// If using JS implementation of RakNet, should we use workers? (This only affects the client)
useRaknetWorkers: true,
// server: What compression algorithm to use by default, either `none`, `deflate` or `snappy`
compressionAlgorithm: 'deflate',
// server and client: On Deflate, what compression level to use, between 1 and 9
compressionLevel: 7,
// server: If true, only compress if a payload is larger than compressionThreshold
compressionThreshold: 512
}
function validateOptions (options) {
if (!Versions[options.version]) {
console.warn('Supported versions', Versions)
throw Error(`Unsupported version ${options.version}`)
}
options.protocolVersion = Versions[options.version]
if (options.protocolVersion < MIN_VERSION) {
throw new Error(`Protocol version < ${MIN_VERSION} : ${options.protocolVersion}, too old`)
}
if (options.useNativeRaknet === true) options.raknetBackend = 'raknet-native'
if (options.useNativeRaknet === false) options.raknetBackend = 'jsp-raknet'
}
module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION, Versions, validateOptions, testedVersions }

270
src/rak.js Normal file
View file

@ -0,0 +1,270 @@
const { EventEmitter } = require('events')
const ConnWorker = require('./rakWorker')
const { waitFor } = require('./datatypes/util')
let Client, Server, PacketPriority, EncapsulatedPacket, PacketReliability, Reliability
class RakTimeout extends Error {};
function setBackend (backend) {
// We have to explicitly require the backend for bundlers
switch (backend) {
case 'raknet-node':
({ Client, Server, PacketPriority, PacketReliability } = require('raknet-node'))
return { RakServer: RakNativeServer, RakClient: RakNativeClient, RakTimeout }
case 'raknet-native':
({ Client, Server, PacketPriority, PacketReliability } = require('raknet-native'))
return { RakServer: RakNativeServer, RakClient: RakNativeClient, RakTimeout }
case 'jsp-raknet':
({ Client, Server, EncapsulatedPacket, Reliability } = require('jsp-raknet'))
return { RakServer: RakJsServer, RakClient: RakJsClient, RakTimeout }
}
}
module.exports = (backend) => {
if (backend) {
return setBackend(backend)
} else {
try {
return setBackend('raknet-native')
} catch (e) {
console.debug(`[raknet] ${backend} library not found, defaulting to jsp-raknet. Correct the "raknetBackend" option to avoid this error.`, e)
return setBackend('jsp-raknet')
}
}
}
class RakNativeClient extends EventEmitter {
constructor (options, client) {
super()
this.connected = false
this.onConnected = () => { }
this.onCloseConnection = () => { }
this.onEncapsulated = () => { }
const protocolVersion = client?.versionGreaterThanOrEqualTo('1.19.30') ? 11 : 10
this.raknet = new Client(options.host, options.port, { protocolVersion })
this.raknet.on('encapsulated', ({ buffer, address }) => {
if (this.connected) { // Discard packets that are queued to be sent to us after close
this.onEncapsulated(buffer, address)
}
})
this.raknet.on('connect', () => {
this.connected = true
this.onConnected()
})
this.raknet.on('disconnect', ({ reason }) => {
this.connected = false
this.onCloseConnection(reason)
})
}
async ping (timeout = 1000) {
this.raknet.ping()
return waitFor((done) => {
this.raknet.on('pong', (ret) => {
if (ret.extra) {
done(ret.extra.toString())
}
})
}, timeout, () => {
if ('REPLIT_ENVIRONMENT' in process.env) {
console.warn('A Replit environment was detected. Replit may not support the necessary outbound UDP connections required to connect to a Minecraft server. Please see https://github.com/PrismarineJS/bedrock-protocol/blob/master/docs/FAQ.md for more information.')
}
throw new RakTimeout('Ping timed out')
})
}
connect () {
this.raknet.connect()
}
close () {
this.connected = false
setTimeout(() => {
this.raknet.close()
}, 40)
}
sendReliable (buffer, immediate) {
if (!this.connected) return
const priority = immediate ? PacketPriority.IMMEDIATE_PRIORITY : PacketPriority.MEDIUM_PRIORITY
return this.raknet.send(buffer, priority, PacketReliability.RELIABLE_ORDERED, 0)
}
}
class RakNativeServer extends EventEmitter {
constructor (options = {}, server) {
super()
this.onOpenConnection = () => { }
this.onCloseConnection = () => { }
this.onEncapsulated = () => { }
this.raknet = new Server(options.host, options.port, {
maxConnections: options.maxPlayers || 3,
protocolVersion: server.versionLessThan('1.19.30') ? 10 : 11,
message: server.getAdvertisement().toBuffer()
})
this.onClose = () => {}
this.updateAdvertisement = () => {
this.raknet.setOfflineMessage(server.getAdvertisement().toBuffer())
}
this.raknet.on('openConnection', (client) => {
client.sendReliable = function (buffer, immediate) {
const priority = immediate ? PacketPriority.IMMEDIATE_PRIORITY : PacketPriority.MEDIUM_PRIORITY
return this.send(buffer, priority, PacketReliability.RELIABLE_ORDERED, 0)
}
this.onOpenConnection(client)
})
this.raknet.on('closeConnection', (client) => {
this.onCloseConnection(client)
})
this.raknet.on('encapsulated', ({ buffer, address }) => {
this.onEncapsulated(buffer, address)
})
this.raknet.on('close', (reason) => this.onClose(reason))
}
listen () {
this.raknet.listen()
}
close () {
this.raknet.close()
}
}
class RakJsClient extends EventEmitter {
constructor (options = {}) {
super()
this.options = options
this.onConnected = () => { }
this.onCloseConnection = () => { }
this.onEncapsulated = () => { }
if (options.useWorkers) {
this.connect = this.workerConnect
this.close = reason => this.worker?.postMessage({ type: 'close', reason })
this.sendReliable = this.workerSendReliable
} else {
this.connect = this.plainConnect
this.close = reason => this.raknet.close(reason)
this.sendReliable = this.plainSendReliable
}
this.pongCb = null
}
workerConnect (host = this.options.host, port = this.options.port) {
this.worker = ConnWorker.connect(host, port)
this.worker.on('message', (evt) => {
switch (evt.type) {
case 'connected': {
this.onConnected()
break
}
case 'encapsulated': {
const [ecapsulated, address] = evt.args
this.onEncapsulated(ecapsulated, address.hash)
break
}
case 'pong':
this.pongCb?.(evt.args)
break
case 'disconnect':
this.onCloseConnection()
break
}
})
}
async plainConnect (host = this.options.host, port = this.options.port) {
this.raknet = new Client(host, port)
await this.raknet.connect()
this.raknet.on('connecting', () => {
console.log(`[client] connecting to ${host}/${port}`)
})
this.raknet.on('connected', this.onConnected)
this.raknet.on('encapsulated', (encapsulated, addr) => this.onEncapsulated(encapsulated, addr.hash))
this.raknet.on('disconnect', (reason) => this.onCloseConnection(reason))
}
workerSendReliable (buffer, immediate) {
this.worker.postMessage({ type: 'queueEncapsulated', packet: buffer, immediate })
}
plainSendReliable (buffer, immediate) {
const sendPacket = new EncapsulatedPacket()
sendPacket.reliability = Reliability.ReliableOrdered
sendPacket.buffer = buffer
this.raknet.connection.addEncapsulatedToQueue(sendPacket)
if (immediate) this.raknet.connection.sendQueue()
}
async ping (timeout = 1000) {
if (this.worker) {
this.worker.postMessage({ type: 'ping' })
return waitFor(res => {
this.pongCb = data => res(data)
}, timeout, () => { throw new RakTimeout('Ping timed out') })
} else {
if (!this.raknet) this.raknet = new Client(this.options.host, this.options.port)
return waitFor(res => {
this.raknet.ping(data => {
this.raknet.close()
res(data)
})
}, timeout, () => { throw new RakTimeout('Ping timed out') })
}
}
}
class RakJsServer extends EventEmitter {
constructor (options = {}, server) {
super()
this.options = options
this.server = server
this.onOpenConnection = () => { }
this.onCloseConnection = () => { }
this.onEncapsulated = (packet, address) => server.onEncapsulated(packet.buffer, address)
this.onClose = () => {}
this.updateAdvertisement = () => {
this.raknet.setPongAdvertisement(server.getAdvertisement())
}
if (options.useWorkers) {
throw Error('nyi')
} else {
this.listen = this.plainListen
}
}
async plainListen () {
this.raknet = new Server(this.options.host, this.options.port, this.server.getAdvertisement())
await this.raknet.listen(this.options.host, this.options.port)
this.raknet.on('openConnection', (conn) => {
conn.sendReliable = (buffer, immediate) => {
const sendPacket = new EncapsulatedPacket()
sendPacket.reliability = Reliability.ReliableOrdered
sendPacket.buffer = buffer
conn.addEncapsulatedToQueue(sendPacket, immediate ? 1 : 0)
}
this.onOpenConnection(conn)
})
this.raknet.on('closeConnection', this.onCloseConnection)
this.raknet.on('encapsulated', this.onEncapsulated)
this.raknet.on('close', this.onClose)
}
close () {
// Allow some time for the final packets to come in/out
setTimeout(() => {
this.raknet.close()
}, 40)
}
}

69
src/rakWorker.js Normal file
View file

@ -0,0 +1,69 @@
const { Worker, isMainThread, parentPort } = require('worker_threads')
const { Client, EncapsulatedPacket, Reliability } = require('jsp-raknet')
const debug = require('debug')('minecraft-protocol')
function connect (host, port) {
if (isMainThread) {
const worker = new Worker(__filename)
worker.postMessage({ type: 'connect', host, port })
return worker
}
}
let raknet
function main () {
parentPort.on('message', (evt) => {
if (evt.type === 'connect') {
const { host, port } = evt
raknet = new Client(host, port)
raknet.connect().then(() => {
debug('Raknet Connected!')
})
raknet.on('connecting', () => {
debug(`[client] connecting to ${host}/${port}`)
parentPort.postMessage('message', { type: 'connecting' })
})
raknet.once('connected', (connection) => {
debug('[worker] connected!')
globalThis.raknetConnection = connection
parentPort.postMessage({ type: 'connected' })
})
raknet.on('encapsulated', (...args) => {
parentPort.postMessage({ type: 'encapsulated', args })
})
raknet.on('disconnect', (reason) => {
debug('[worker] disconnected!')
parentPort.postMessage({ type: 'disconnect', reason })
})
raknet.on('raw', (buffer, inetAddr) => {
debug('Raw packet', buffer, inetAddr)
})
} else if (evt.type === 'queueEncapsulated') {
const sendPacket = new EncapsulatedPacket()
sendPacket.reliability = Reliability.ReliableOrdered
sendPacket.buffer = evt.packet
globalThis.raknetConnection?.addEncapsulatedToQueue(sendPacket)
if (evt.immediate) {
globalThis.raknetConnection?.sendQueue()
}
} else if (evt.type === 'close') {
raknet.close()
process.exit(0)
} else if (evt.type === 'ping') {
raknet.ping((args) => {
parentPort.postMessage({ type: 'pong', args })
})
}
})
}
if (!isMainThread) main()
module.exports = { connect }

292
src/relay.js Normal file
View file

@ -0,0 +1,292 @@
const { Client } = require('./client')
const { Server } = require('./server')
const { Player } = require('./serverPlayer')
const { realmAuthenticate } = require('./client/auth')
const debug = globalThis.isElectron ? console.debug : require('debug')('minecraft-protocol')
const debugging = false // Do re-encoding tests
class RelayPlayer extends Player {
constructor (server, conn) {
super(server, conn)
this.startRelaying = false
this.once('join', () => { // The client has joined our proxy
this.flushDownQueue() // Send queued packets from the upstream backend
this.startRelaying = true
})
this.downQ = []
this.upQ = []
this.upInLog = (...msg) => console.debug('* Backend -> Proxy', ...msg)
this.upOutLog = (...msg) => console.debug('* Proxy -> Backend', ...msg)
this.downInLog = (...msg) => console.debug('* Client -> Proxy', ...msg)
this.downOutLog = (...msg) => console.debug('* Proxy -> Client', ...msg)
if (!server.options.logging) {
this.upInLog = () => { }
this.upOutLog = () => { }
this.downInLog = () => { }
this.downOutLog = () => { }
}
this.outLog = this.downOutLog
this.inLog = this.downInLog
this.chunkSendCache = []
this.sentStartGame = false
this.respawnPacket = []
}
// Called when we get a packet from backend server (Backend -> PROXY -> Client)
readUpstream (packet) {
if (!this.startRelaying) {
this.upInLog('Client not ready, queueing packet until join')
this.downQ.push(packet)
return
}
let des
try {
des = this.server.deserializer.parsePacketBuffer(packet)
} catch (e) {
this.server.deserializer.dumpFailedBuffer(packet, this.connection.address)
console.error(this.connection.address, e)
if (!this.options.omitParseErrors) {
this.disconnect('Server packet parse error')
}
return
}
const name = des.data.name
const params = des.data.params
this.upInLog('->', name, params)
if (name === 'play_status' && params.status === 'login_success') return // Already sent this, this needs to be sent ASAP or client will disconnect
if (debugging) { // some packet encode/decode testing stuff
this.server.deserializer.verify(des, this.server.serializer)
}
this.emit('clientbound', des.data, des)
if (!des.canceled) {
if (name === 'start_game') {
setTimeout(() => {
this.sentStartGame = true
}, 500)
} else if (name === 'level_chunk' && !this.sentStartGame) {
this.chunkSendCache.push(params)
return
}
this.queue(name, params)
}
if (this.chunkSendCache.length > 0 && this.sentStartGame) {
for (const entry of this.chunkSendCache) {
this.queue('level_chunk', entry)
}
this.chunkSendCache = []
}
}
// Send queued packets to the connected client
flushDownQueue () {
this.downOutLog('Flushing downstream queue')
for (const packet of this.downQ) {
const des = this.server.deserializer.parsePacketBuffer(packet)
this.write(des.data.name, des.data.params)
}
this.downQ = []
}
// Send queued packets to the backend upstream server from the client
flushUpQueue () {
this.upOutLog('Flushing upstream queue')
for (const e of this.upQ) { // Send the queue
const des = this.server.deserializer.parsePacketBuffer(e)
if (des.data.name === 'client_cache_status') {
// Currently not working, force off the chunk cache
} else {
this.upstream.write(des.data.name, des.data.params)
}
}
this.upQ = []
}
// Called when the server gets a packet from the downstream player (Client -> PROXY -> Backend)
readPacket (packet) {
// The downstream client conn is established & we got a packet to send to upstream server
if (this.startRelaying) {
// Upstream is still connecting/handshaking
if (!this.upstream) {
const des = this.server.deserializer.parsePacketBuffer(packet)
this.downInLog('Got downstream connected packet but upstream is not connected yet, added to q', des)
this.upQ.push(packet) // Put into a queue
return
}
// Send queued packets
this.flushUpQueue()
this.downInLog('recv', packet)
// TODO: If we fail to parse a packet, proxy it raw and log an error
const des = this.server.deserializer.parsePacketBuffer(packet)
if (debugging) { // some packet encode/decode testing stuff
this.server.deserializer.verify(des, this.server.serializer)
}
this.emit('serverbound', des.data, des)
if (des.canceled) return
switch (des.data.name) {
case 'client_cache_status':
// Force the chunk cache off.
this.upstream.queue('client_cache_status', { enabled: this.enableChunkCaching })
break
case 'set_local_player_as_initialized':
this.status = 3
// falls through
default:
// Emit the packet as-is back to the upstream server
this.downInLog('Relaying', des.data)
this.upstream.queue(des.data.name, des.data.params)
}
} else {
super.readPacket(packet)
}
}
close (reason) {
this.upstream?.close(reason)
super.close(reason)
}
}
class Relay extends Server {
/**
* Creates a new non-transparent proxy connection to a destination server
* @param {Options} options
*/
constructor (options) {
super(options)
this.RelayPlayer = options.relayPlayer || RelayPlayer
this.forceSingle = options.forceSingle
this.upstreams = new Map()
this.conLog = debug
this.enableChunkCaching = options.enableChunkCaching
}
// Called after a new player joins our proxy. We first create a new Client to connect to
// the remote server. Then we listen to some events and proxy them over. The queue and
// flushing logic is more of an accessory to make sure the server or client recieves
// a packet, no matter what state it's in. For example, if the client wants to send a
// packet to the server but it's not connected, it will add to the queue and send as soon
// as a connection with the server is established.
async openUpstreamConnection (ds, clientAddr) {
const options = {
authTitle: this.options.authTitle,
flow: this.options.flow,
deviceType: this.options.deviceType,
offline: this.options.destination.offline ?? this.options.offline,
username: this.options.offline ? ds.profile.name : ds.profile.xuid,
version: this.options.version,
realms: this.options.destination.realms,
host: this.options.destination.host,
port: this.options.destination.port,
batchingInterval: this.options.batchingInterval,
onMsaCode: (code) => {
if (this.options.onMsaCode) {
this.options.onMsaCode(code, ds)
} else {
ds.disconnect("It's your first time joining. Please sign in and reconnect to join this server:\n\n" + code.message)
}
},
profilesFolder: this.options.profilesFolder,
backend: this.options.backend,
autoInitPlayer: false
}
if (this.options.destination.realms) {
await realmAuthenticate(options)
}
const client = new Client(options)
// Set the login payload unless `noLoginForward` option
if (!client.noLoginForward) client.options.skinData = ds.skinData
client.ping().then(pongData => {
client.connect()
}).catch(err => {
this.emit('error', err)
})
this.conLog('Connecting to', options.host, options.port)
client.outLog = ds.upOutLog
client.inLog = ds.upInLog
client.once('join', () => {
// Tell the server to disable chunk cache for this connection as a client.
// Wait a bit for the server to ack and process, the continue with proxying
// otherwise the player can get stuck in an empty world.
client.write('client_cache_status', { enabled: this.enableChunkCaching })
ds.upstream = client
ds.flushUpQueue()
this.conLog('Connected to upstream server')
client.readPacket = (packet) => ds.readUpstream(packet)
this.emit('join', /* client connected to proxy */ ds, /* backend server */ client)
})
client.on('error', (err) => {
ds.disconnect('Server error: ' + err.message)
debug(clientAddr, 'was disconnected because of error', err)
this.upstreams.delete(clientAddr.hash)
})
client.on('close', (reason) => {
ds.disconnect('Backend server closed connection')
this.upstreams.delete(clientAddr.hash)
})
this.upstreams.set(clientAddr.hash, client)
}
// Close a connection to a remote backend server.
closeUpstreamConnection (clientAddr) {
const up = this.upstreams.get(clientAddr.hash)
if (!up) throw Error(`unable to close non-open connection ${clientAddr.hash}`)
up.close()
this.upstreams.delete(clientAddr.hash)
this.conLog('closed upstream connection', clientAddr)
}
// Called when a new player connects to our proxy server. Once the player has authenticated,
// we can open an upstream connection to the backend server.
onOpenConnection = (conn) => {
if (this.forceSingle && this.clientCount > 0) {
this.conLog('dropping connection as single client relay', conn)
conn.close()
} else {
this.clientCount++
const player = new this.RelayPlayer(this, conn)
this.conLog('New connection from', conn.address)
this.clients[conn.address] = player
this.emit('connect', player)
player.on('login', () => {
this.openUpstreamConnection(player, conn.address)
})
player.on('close', (reason) => {
this.conLog('player disconnected', conn.address, reason)
this.clientCount--
delete this.clients[conn.address]
})
}
}
// When our server is closed, make sure to kick all of the connected clients and run emitters.
close (...a) {
for (const [, v] of this.upstreams) {
v.close(...a)
}
super.close(...a)
}
}
// Too many things called 'Proxy' ;)
module.exports = { Relay }

160
src/server.js Normal file
View file

@ -0,0 +1,160 @@
const { EventEmitter } = require('events')
const { createDeserializer, createSerializer } = require('./transforms/serializer')
const { Player } = require('./serverPlayer')
const { sleep } = require('./datatypes/util')
const { ServerAdvertisement } = require('./server/advertisement')
const Options = require('./options')
const debug = globalThis.isElectron ? console.debug : require('debug')('minecraft-protocol')
class Server extends EventEmitter {
constructor (options) {
super()
this.options = { ...Options.defaultOptions, ...options }
this.validateOptions()
this.RakServer = require('./rak')(this.options.raknetBackend).RakServer
this._loadFeatures(this.options.version)
this.serializer = createSerializer(this.options.version)
this.deserializer = createDeserializer(this.options.version)
this.advertisement = new ServerAdvertisement(this.options.motd, this.options.port, this.options.version)
this.advertisement.playersMax = options.maxPlayers ?? 3
/** @type {Object<string, Player>} */
this.clients = {}
this.clientCount = 0
this.conLog = debug
this.batchHeader = 0xfe
this.setCompressor(this.options.compressionAlgorithm, this.options.compressionLevel, this.options.compressionThreshold)
}
_loadFeatures (version) {
try {
const mcData = require('minecraft-data')('bedrock_' + version)
this.features = {
compressorInHeader: mcData.supportFeature('compressorInPacketHeader'),
newLoginIdentityFields: mcData.supportFeature('newLoginIdentityFields')
}
} catch (e) {
throw new Error(`Unsupported version: '${version}', no data available`)
}
}
setCompressor (algorithm, level = 1, threshold = 256) {
switch (algorithm) {
case 'none':
this.compressionAlgorithm = 'none'
this.compressionLevel = 0
this.compressionHeader = 255
break
case 'deflate':
this.compressionAlgorithm = 'deflate'
this.compressionLevel = level
this.compressionThreshold = threshold
this.compressionHeader = 0
break
case 'snappy':
this.compressionAlgorithm = 'snappy'
this.compressionLevel = level
this.compressionThreshold = threshold
this.compressionHeader = 1
break
default:
throw new Error(`Unknown compression algorithm: ${algorithm}`)
}
}
validateOptions () {
Options.validateOptions(this.options)
}
versionLessThan (version) {
return this.options.protocolVersion < (typeof version === 'string' ? Options.Versions[version] : version)
}
versionGreaterThan (version) {
return this.options.protocolVersion > (typeof version === 'string' ? Options.Versions[version] : version)
}
versionGreaterThanOrEqualTo (version) {
return this.options.protocolVersion >= (typeof version === 'string' ? Options.Versions[version] : version)
}
onOpenConnection = (conn) => {
this.conLog('New connection: ', conn?.address)
const player = new Player(this, conn)
this.clients[conn.address] = player
this.clientCount++
this.emit('connect', player)
}
onCloseConnection = (conn, reason) => {
this.conLog('Connection closed: ', conn.address, reason)
this.clients[conn.address]?.close()
delete this.clients[conn.address]
this.clientCount--
}
onEncapsulated = (buffer, address) => {
const client = this.clients[address]
if (!client) {
// Ignore packets from clients that are not connected.
debug(`Ignoring packet from unknown inet address: ${address}`)
return
}
process.nextTick(() => client.handle(buffer))
}
getAdvertisement () {
if (this.options.advertisementFn) {
return this.options.advertisementFn()
}
this.advertisement.playersOnline = this.clientCount
return this.advertisement
}
async listen () {
const { host, port, maxPlayers } = this.options
this.raknet = new this.RakServer({ host, port, maxPlayers }, this)
try {
await this.raknet.listen()
} catch (e) {
console.warn(`Failed to bind server on [${this.options.host}]/${this.options.port}, is the port free?`)
throw e
}
this.conLog('Listening on', host, port, this.options.version)
this.raknet.onOpenConnection = this.onOpenConnection
this.raknet.onCloseConnection = this.onCloseConnection
this.raknet.onEncapsulated = this.onEncapsulated
this.raknet.onClose = (reason) => this.close(reason || 'Raknet closed')
this.serverTimer = setInterval(() => {
this.raknet.updateAdvertisement()
}, 1000)
return { host, port }
}
async close (disconnectReason = 'Server closed') {
for (const caddr in this.clients) {
const client = this.clients[caddr]
client.disconnect(disconnectReason)
}
clearInterval(this.serverTimer)
this.clients = {}
this.clientCount = 0
// Allow some time for client to get disconnect before closing connection.
await sleep(60)
this.raknet.close()
}
}
module.exports = { Server }

View file

@ -0,0 +1,70 @@
const { Versions, CURRENT_VERSION } = require('../options')
class ServerAdvertisement {
motd = 'Bedrock Protocol Server'
levelName = 'bedrock-protocol'
playersOnline = 0
playersMax = 5
gamemode = 'Creative'
serverId = Date.now().toString()
gamemodeId = 1
portV4 = undefined
portV6 = undefined
constructor (obj, port, version = CURRENT_VERSION) {
if (obj?.name) obj.motd = obj.name
this.protocol = Versions[version]
this.version = version
this.portV4 = port
this.portV6 = port
Object.assign(this, obj)
}
fromString (str) {
const [header, motd, protocol, version, playersOnline, playersMax, serverId, levelName, gamemode, gamemodeId, portV4, portV6] = str.split(';')
Object.assign(this, { header, motd, protocol, version, playersOnline, playersMax, serverId, levelName, gamemode, gamemodeId, portV4, portV6 })
for (const numeric of ['playersOnline', 'playersMax', 'gamemodeId', 'portV4', 'portV6']) {
if (this[numeric] !== undefined) {
this[numeric] = this[numeric] ? parseInt(this[numeric]) : null
}
}
return this
}
toString () {
return [
'MCPE',
this.motd,
this.protocol,
this.version,
this.playersOnline,
this.playersMax,
this.serverId,
this.levelName,
this.gamemode,
this.gamemodeId,
this.portV4,
this.portV6,
'0'
].join(';') + ';'
}
toBuffer (version) {
const str = this.toString(version)
const length = Buffer.byteLength(str)
const buf = Buffer.alloc(2 + length)
buf.writeUInt16BE(length, 0)
buf.write(str, 2)
return buf
}
}
module.exports = {
ServerAdvertisement,
getServerName (client) {
return new ServerAdvertisement().toBuffer()
},
fromServerName (string) {
return new ServerAdvertisement().fromString(string)
}
}

203
src/serverPlayer.js Normal file
View file

@ -0,0 +1,203 @@
const { ClientStatus, Connection } = require('./connection')
const Options = require('./options')
const { serialize, isDebug } = require('./datatypes/util')
const { KeyExchange } = require('./handshake/keyExchange')
const Login = require('./handshake/login')
const LoginVerify = require('./handshake/loginVerify')
const debug = require('debug')('minecraft-protocol')
class Player extends Connection {
constructor (server, connection) {
super()
this.server = server
this.features = server.features
this.serializer = server.serializer
this.deserializer = server.deserializer
this.connection = connection
this.options = server.options
KeyExchange(this, server, server.options)
Login(this, server, server.options)
LoginVerify(this, server, server.options)
this.startQueue()
this.status = ClientStatus.Authenticating
if (isDebug) {
this.inLog = (...args) => debug('-> S', ...args)
this.outLog = (...args) => debug('<- S', ...args)
}
this.batchHeader = this.server.batchHeader
// Compression is server-wide
this.compressionAlgorithm = this.server.compressionAlgorithm
this.compressionLevel = this.server.compressionLevel
this.compressionThreshold = this.server.compressionThreshold
this.compressionHeader = this.server.compressionHeader
this._sentNetworkSettings = false // 1.19.30+
}
getUserData () {
return this.userData
}
sendNetworkSettings () {
this.write('network_settings', {
compression_threshold: this.server.compressionThreshold,
compression_algorithm: this.server.compressionAlgorithm,
client_throttle: false,
client_throttle_threshold: 0,
client_throttle_scalar: 0
})
this._sentNetworkSettings = true
this.compressionReady = true
}
handleClientProtocolVersion (clientVersion) {
if (this.server.options.protocolVersion) {
if (this.server.options.protocolVersion < clientVersion) {
this.sendDisconnectStatus('failed_spawn') // client too new
return false
}
} else if (clientVersion < Options.MIN_VERSION) {
this.sendDisconnectStatus('failed_client') // client too old
return false
}
return true
}
onLogin (packet) {
const body = packet.data
this.emit('loggingIn', body)
const clientVer = body.params.protocol_version
if (!this.handleClientProtocolVersion(clientVer)) {
return
}
// Parse login data
const tokens = body.params.tokens
try {
const skinChain = tokens.client
const authChain = JSON.parse(tokens.identity)
let chain
if (authChain.Certificate) { // 1.21.90+
chain = JSON.parse(authChain.Certificate).chain
} else if (authChain.chain) {
chain = authChain.chain
} else {
throw new Error('Invalid login packet: missing chain or Certificate')
}
var { key, userData, skinData } = this.decodeLoginJWT(chain, skinChain) // eslint-disable-line
} catch (e) {
debug(this.address, e)
this.disconnect('Server authentication error')
return
}
this.emit('server.client_handshake', { key }) // internal so we start encryption
this.userData = userData.extraData
this.skinData = skinData
this.profile = {
name: userData.extraData?.displayName,
uuid: userData.extraData?.identity,
xuid: userData.extraData?.xuid || userData.extraData?.XUID
}
this.version = clientVer
this.emit('login', { user: userData.extraData }) // emit events for user
}
/**
* Disconnects a client before it has joined
* @param {string} playStatus
*/
sendDisconnectStatus (playStatus) {
if (this.status === ClientStatus.Disconnected) return
this.write('play_status', { status: playStatus })
this.close('kick')
}
/**
* Disconnects a client
*/
disconnect (reason = 'Server closed', hide = false) {
if (this.status === ClientStatus.Disconnected) return
this.write('disconnect', {
hide_disconnect_screen: hide,
message: reason,
filtered_message: ''
})
this.server.conLog('Kicked ', this.connection?.address, reason)
setTimeout(() => this.close('kick'), 100) // Allow time for message to be recieved.
}
// After sending Server to Client Handshake, this handles the client's
// Client to Server handshake response. This indicates successful encryption
onHandshake () {
// https://wiki.vg/Bedrock_Protocol#Play_Status
this.write('play_status', { status: 'login_success' })
this.status = ClientStatus.Initializing
this.emit('join')
}
close (reason) {
if (this.status !== ClientStatus.Disconnected) {
this.emit('close') // Emit close once
if (!reason) this.inLog?.('Client closed connection', this.connection?.address)
}
this.q = []
this.q2 = []
clearInterval(this.loop)
this.connection?.close()
this.removeAllListeners()
this.status = ClientStatus.Disconnected
}
readPacket (packet) {
try {
var des = this.server.deserializer.parsePacketBuffer(packet) // eslint-disable-line
} catch (e) {
this.disconnect('Server error')
debug('Dropping packet from', this.connection.address, e)
return
}
this.inLog?.(des.data.name, serialize(des.data.params))
switch (des.data.name) {
// This is the first packet on 1.19.30 & above
case 'request_network_settings':
if (this.handleClientProtocolVersion(des.data.params.client_protocol)) {
this.sendNetworkSettings()
this.compressionLevel = this.server.compressionLevel
}
return
// Below 1.19.30, this is the first packet.
case 'login':
this.onLogin(des)
if (!this._sentNetworkSettings) this.sendNetworkSettings()
return
case 'client_to_server_handshake':
// Emit the 'join' event
this.onHandshake()
break
case 'set_local_player_as_initialized':
this.status = ClientStatus.Initialized
this.inLog?.('Server client spawned')
// Emit the 'spawn' event
this.emit('spawn')
break
default:
if (this.status === ClientStatus.Disconnected || this.status === ClientStatus.Authenticating) {
this.inLog?.('ignoring', des.data.name)
return
}
}
this.emit(des.data.name, des.data.params)
this.emit('packet', des)
}
}
module.exports = { Player }

Binary file not shown.

View file

@ -0,0 +1,104 @@
const crypto = require('crypto')
const Zlib = require('zlib')
function createCipher (secret, initialValue, cipherAlgorithm) {
if (crypto.getCiphers().includes(cipherAlgorithm)) {
return crypto.createCipheriv(cipherAlgorithm, secret, initialValue)
}
}
function createDecipher (secret, initialValue, cipherAlgorithm) {
if (crypto.getCiphers().includes(cipherAlgorithm)) {
return crypto.createDecipheriv(cipherAlgorithm, secret, initialValue)
}
}
function computeCheckSum (packetPlaintext, sendCounter, secretKeyBytes) {
const digest = crypto.createHash('sha256')
const counter = Buffer.alloc(8)
counter.writeBigInt64LE(sendCounter, 0)
digest.update(counter)
digest.update(packetPlaintext)
digest.update(secretKeyBytes)
const hash = digest.digest()
return hash.slice(0, 8)
}
function createEncryptor (client, iv) {
if (client.versionLessThan('1.16.220')) {
client.cipher = createCipher(client.secretKeyBytes, iv, 'aes-256-cfb8')
} else {
client.cipher = createCipher(client.secretKeyBytes, iv.slice(0, 12), 'aes-256-gcm')
}
client.sendCounter = client.sendCounter || 0n
// A packet is encrypted via AES256(plaintext + SHA256(send_counter + plaintext + secret_key)[0:8]).
// The send counter is represented as a little-endian 64-bit long and incremented after each packet.
function process (chunk) {
const compressed = Zlib.deflateRawSync(chunk, { level: client.compressionLevel })
const buffer = client.features.compressorInHeader
? Buffer.concat([Buffer.from([0]), compressed])
: compressed
const packet = Buffer.concat([buffer, computeCheckSum(buffer, client.sendCounter, client.secretKeyBytes)])
client.sendCounter++
client.cipher.write(packet)
}
client.cipher.on('data', client.onEncryptedPacket)
return (blob) => {
process(blob)
}
}
function createDecryptor (client, iv) {
if (client.versionLessThan('1.16.220')) {
client.decipher = createDecipher(client.secretKeyBytes, iv, 'aes-256-cfb8')
} else {
client.decipher = createDecipher(client.secretKeyBytes, iv.slice(0, 12), 'aes-256-gcm')
}
client.receiveCounter = client.receiveCounter || 0n
function verify (chunk) {
const packet = chunk.slice(0, chunk.length - 8)
const checksum = chunk.slice(chunk.length - 8, chunk.length)
const computedCheckSum = computeCheckSum(packet, client.receiveCounter, client.secretKeyBytes)
client.receiveCounter++
if (!checksum.equals(computedCheckSum)) {
client.emit('error', Error(`Checksum mismatch ${checksum.toString('hex')} != ${computedCheckSum.toString('hex')}`))
client.disconnect('disconnectionScreen.badPacket')
return
}
let buffer
if (client.features.compressorInHeader) {
switch (packet[0]) {
case 0:
buffer = Zlib.inflateRawSync(packet.slice(1), { chunkSize: 512000 })
break
case 255:
buffer = packet.slice(1)
break
default:
client.emit('error', Error(`Unsupported compressor: ${packet[0]}`))
}
} else {
buffer = Zlib.inflateRawSync(packet, { chunkSize: 512000 })
}
client.onDecryptedPacket(buffer)
}
client.decipher.on('data', verify)
return (blob) => {
client.decipher.write(blob)
}
}
module.exports = {
createCipher, createDecipher, createEncryptor, createDecryptor
}

111
src/transforms/framer.js Normal file
View file

@ -0,0 +1,111 @@
const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint
const zlib = require('zlib')
// Concatenates packets into one batch packet, and adds length prefixs.
class Framer {
constructor (client) {
// Encoding
this.packets = []
this.batchHeader = client.batchHeader
this.compressor = client.compressionAlgorithm || 'none'
this.compressionLevel = client.compressionLevel
this.compressionThreshold = client.compressionThreshold
this.compressionHeader = client.compressionHeader || 0
this.writeCompressor = client.features.compressorInHeader && client.compressionReady
}
// No compression in base class
compress (buffer) {
switch (this.compressor) {
case 'deflate': return zlib.deflateRawSync(buffer, { level: this.compressionLevel })
case 'snappy': throw Error('Snappy compression not implemented')
case 'none': return buffer
}
}
static decompress (algorithm, buffer) {
switch (algorithm) {
case 0:
case 'deflate':
return zlib.inflateRawSync(buffer, { chunkSize: 512000 })
case 1:
case 'snappy':
throw Error('Snappy compression not implemented')
case 'none':
case 255:
return buffer
default: throw Error('Unknown compression type ' + algorithm)
}
}
static decode (client, buf) {
// Read header
if (this.batchHeader && buf[0] !== this.batchHeader) throw Error(`bad batch packet header, received: ${buf[0]}, expected: ${this.batchHeader}`)
const buffer = buf.slice(1)
// Decompress
let decompressed
if (client.features.compressorInHeader && client.compressionReady) {
decompressed = this.decompress(buffer[0], buffer.slice(1))
} else {
// On old versions, compressor is session-wide ; failing to decompress
// a packet will assume it's not compressed
try {
decompressed = this.decompress(client.compressionAlgorithm, buffer)
} catch (e) {
decompressed = buffer
}
}
return Framer.getPackets(decompressed)
}
encode () {
const buf = Buffer.concat(this.packets)
const shouldCompress = buf.length > this.compressionThreshold
const header = this.batchHeader ? [this.batchHeader] : []
if (this.writeCompressor) header.push(shouldCompress ? this.compressionHeader : 255)
return Buffer.concat([Buffer.from(header), shouldCompress ? this.compress(buf) : buf])
}
addEncodedPacket (chunk) {
const varIntSize = sizeOfVarInt(chunk.byteLength)
const buffer = Buffer.allocUnsafe(varIntSize + chunk.byteLength)
writeVarInt(chunk.length, buffer, 0)
chunk.copy(buffer, varIntSize)
this.packets.push(buffer)
}
addEncodedPackets (packets) {
let allocSize = 0
for (const packet of packets) {
allocSize += sizeOfVarInt(packet.byteLength)
allocSize += packet.byteLength
}
const buffer = Buffer.allocUnsafe(allocSize)
let offset = 0
for (const chunk of packets) {
offset = writeVarInt(chunk.length, buffer, offset)
offset += chunk.copy(buffer, offset, 0)
}
this.packets.push(buffer)
}
getBuffer () {
return Buffer.concat(this.packets)
}
static getPackets (buffer) {
const packets = []
let offset = 0
while (offset < buffer.byteLength) {
const { value, size } = readVarInt(buffer, offset)
const dec = Buffer.allocUnsafe(value)
offset += size
offset += buffer.copy(dec, 0, offset, offset + value)
packets.push(dec)
}
return packets
}
}
module.exports = { Framer }

View file

@ -1,29 +1,75 @@
var ProtoDef = require('protodef').ProtoDef;
var Serializer = require('protodef').Serializer;
var Parser = require('protodef').Parser;
const { ProtoDefCompiler, CompiledProtodef } = require('protodef').Compiler
const { FullPacketParser, Serializer } = require('protodef')
const { join } = require('path')
const fs = require('fs')
var protocol = require('../../data/protocol.json').types;
class Parser extends FullPacketParser {
dumpFailedBuffer (packet, prefix = '') {
if (packet.length > 1000) {
const now = Date.now()
fs.writeFileSync(now + '_packetReadError.txt', packet.toString('hex'))
console.log(prefix, `Deserialization failure for packet 0x${packet.slice(0, 1).toString('hex')}. Packet buffer saved in ./${now}_packetReadError.txt as buffer was too large (${packet.length} bytes).`)
} else {
console.log(prefix, 'Read failure for 0x' + packet.slice(0, 1).toString('hex'), packet.slice(0, 1000))
}
}
function createProtocol() {
var proto = new ProtoDef();
proto.addTypes(require('../datatypes/minecraft'));
proto.addTypes(protocol);
return proto;
verify (deserialized, serializer) {
const { name, params } = deserialized.data
const oldBuffer = deserialized.fullBuffer
const newBuffer = serializer.createPacketBuffer({ name, params })
if (!newBuffer.equals(oldBuffer)) {
const fs = require('fs')
fs.writeFileSync('new.bin', newBuffer)
fs.writeFileSync('old.bin', oldBuffer)
fs.writeFileSync('failed.json', JSON.stringify(params, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2))
console.warn('Failed to re-encode', name)
}
}
}
function createSerializer() {
var proto = createProtocol();
return new Serializer(proto, 'packet');
// Compiles the ProtoDef schema at runtime
function createProtocol (version) {
// Try and load from .js if available
try { require(`../../data/${version}/size.js`); return getProtocol(version) } catch {}
const protocol = require('minecraft-data')('bedrock_' + version).protocol
const compiler = new ProtoDefCompiler()
compiler.addTypesToCompile(protocol.types)
compiler.addTypes(require('../datatypes/compiler-minecraft'))
const compiledProto = compiler.compileProtoDefSync()
return compiledProto
}
function createDeserializer() {
var proto = createProtocol();
return new Parser(proto, 'packet');
// Loads already generated read/write/sizeof code
function getProtocol (version) {
const compiler = new ProtoDefCompiler()
compiler.addTypes(require(join(__dirname, '../datatypes/compiler-minecraft')))
global.PartialReadError = require('protodef/src/utils').PartialReadError
const compile = (compiler, file) => require(file)(compiler.native)
return new CompiledProtodef(
compile(compiler.sizeOfCompiler, join(__dirname, `../../data/${version}/size.js`)),
compile(compiler.writeCompiler, join(__dirname, `../../data/${version}/write.js`)),
compile(compiler.readCompiler, join(__dirname, `../../data/${version}/read.js`))
)
}
function createSerializer (version) {
const proto = createProtocol(version)
return new Serializer(proto, 'mcpe_packet')
}
function createDeserializer (version) {
const proto = createProtocol(version)
return new Parser(proto, 'mcpe_packet')
}
module.exports = {
createDeserializer: createDeserializer,
createSerializer: createSerializer,
createProtocol: createProtocol
};
createDeserializer,
createSerializer,
createProtocol,
getProtocol
}

221
test/internal.js Normal file
View file

@ -0,0 +1,221 @@
const { Server, Client } = require('../')
const { dumpPackets } = require('../tools/genPacketDumps')
const { ping } = require('../src/createClient')
const { CURRENT_VERSION } = require('../src/options')
const { join } = require('path')
const { waitFor } = require('../src/datatypes/util')
const { getPort } = require('./util')
// First we need to dump some packets that a vanilla server would send a vanilla
// client. Then we can replay those back in our custom server.
function prepare (version) {
return dumpPackets(version)
}
async function startTest (version = CURRENT_VERSION, ok) {
await prepare(version)
// const Item = require('../types/Item')(version)
const port = await getPort()
const server = new Server({ host: '0.0.0.0', port, version, offline: true })
function getPath (packetPath) {
return join(__dirname, `../data/${server.options.version}/${packetPath}`)
}
function get (packetPath) {
return require(getPath('sample/' + packetPath))
}
console.log('Starting internal server')
server.listen()
console.log('Started server')
const pongData = await ping({ host: '127.0.0.1', port })
console.assert(pongData, 'did not get valid pong data from server')
const respawnPacket = get('packets/respawn.json')
const chunks = await requestChunks(version, respawnPacket.x, respawnPacket.z, 1)
let loop
// server logic
server.on('connect', client => {
client.on('join', () => {
console.log('Client joined server', client.getUserData())
client.write('resource_packs_info', {
must_accept: false,
has_scripts: false,
behaviour_packs: [],
world_template: { uuid: '550e8400-e29b-41d4-a716-446655440000', version: '' }, // 1.21.50
texture_packs: [],
resource_pack_links: []
})
client.once('resource_pack_client_response', async rp => {
// Tell the server we will compress everything (>=1 byte)
client.write('network_settings', { compression_threshold: 1 })
// Send some inventory slots
for (let i = 0; i < 3; i++) {
// client.queue('inventory_slot', { window_id: 'armor', slot: 0, item: new Item().toBedrock() })
}
// client.queue('inventory_transaction', get('packets/inventory_transaction.json'))
client.queue('player_list', get('packets/player_list.json'))
client.queue('start_game', get('packets/start_game.json'))
if (client.versionLessThan('1.21.60')) {
client.queue('item_component', { entries: [] })
} else {
client.queue('item_registry', get('packets/item_registry.json'))
}
client.queue('set_spawn_position', get('packets/set_spawn_position.json'))
client.queue('set_time', { time: 5433771 })
client.queue('set_difficulty', { difficulty: 1 })
client.queue('set_commands_enabled', { enabled: true })
if (client.versionLessThan('1.19.10')) {
client.queue('adventure_settings', get('packets/adventure_settings.json'))
}
client.queue('biome_definition_list', get('packets/biome_definition_list.json'))
client.queue('available_entity_identifiers', get('packets/available_entity_identifiers.json'))
client.queue('update_attributes', get('packets/update_attributes.json'))
client.queue('creative_content', get('packets/creative_content.json'))
client.queue('inventory_content', get('packets/inventory_content.json'))
client.queue('player_hotbar', { selected_slot: 3, window_id: 'inventory', select_slot: true })
client.queue('crafting_data', get('packets/crafting_data.json'))
client.queue('available_commands', get('packets/available_commands.json'))
client.queue('chunk_radius_update', { chunk_radius: 5 })
// client.queue('set_entity_data', get('packets/set_entity_data.json'))
client.queue('game_rules_changed', get('packets/game_rules_changed.json'))
client.queue('respawn', get('packets/respawn.json'))
for (const chunk of chunks) {
client.queue('level_chunk', chunk)
}
loop = setInterval(() => {
client.write('network_chunk_publisher_update', { coordinates: { x: 646, y: 130, z: 77 }, radius: 64 })
}, 6500)
setTimeout(() => {
client.write('play_status', { status: 'player_spawn' })
}, 3000)
// Respond to tick synchronization packets
client.on('tick_sync', (packet) => {
client.queue('tick_sync', {
request_time: packet.request_time,
response_time: BigInt(Date.now())
})
})
})
})
})
// client logic
const client = new Client({
host: '127.0.0.1',
port,
username: 'Notch',
version,
offline: true
})
console.log('Started client')
client.once('resource_packs_info', (packet) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
client.once('resource_pack_stack', (stack) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
})
client.queue('client_cache_status', { enabled: false })
client.queue('request_chunk_radius', { chunk_radius: 1 })
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n })
})
client.once('spawn', () => {
console.info('Client spawend!')
setTimeout(() => {
client.close()
server.close().then(() => {
ok?.()
})
}, 500)
clearInterval(loop)
})
client.connect()
}
async function requestChunks (version, x, z, radius) {
const ChunkColumn = require('bedrock-provider').chunk('bedrock_1.17.10')
// const mcData = require('minecraft-data')('1.16')
const cxStart = (x >> 4) - radius
const cxEnd = (x >> 4) + radius
const czStart = (z >> 4) - radius
const czEnd = (z >> 4) + radius
// const stone = mcData.blocksByName.stone
const chunks = []
for (let cx = cxStart; cx < cxEnd; cx++) {
for (let cz = czStart; cz < czEnd; cz++) {
console.log('reading chunk at ', cx, cz)
const cc = new ChunkColumn(x, z)
// Temporarily disable until 1.18 PR in bedrock-provider goes through
// for (let x = 0; x < 16; x++) {
// for (let y = 0; y < 60; y++) {
// for (let z = 0; z < 16; z++) {
// cc.setBlock({ x, y, z }, stone)
// }
// }
// }
if (!cc) {
console.log('no chunk')
continue
}
const cbuf = await cc.networkEncodeNoCache()
chunks.push({
x: cx,
z: cz,
sub_chunk_count: cc.sectionsLen,
cache_enabled: false,
blobs: [],
payload: cbuf
})
}
}
return chunks
}
async function timedTest (version, timeout = 1000 * 220) {
await waitFor((resolve, reject) => {
// mocha eats up stack traces...
startTest(version, resolve).catch(reject)
}, timeout, () => {
throw Error('timed out')
})
console.info('✔ ok')
}
// if (!module.parent) timedTest('1.19.10')
module.exports = { startTest, timedTest, requestChunks }

19
test/internal.test.js Normal file
View file

@ -0,0 +1,19 @@
/* eslint-env jest */
const { timedTest } = require('./internal')
const { testedVersions } = require('../src/options')
const { sleep } = require('../src/datatypes/util')
require('events').captureRejections = true
describe('internal client/server test', function () {
const vcount = testedVersions.length
this.timeout(vcount * 80 * 1000)
for (const version of testedVersions) {
it('connects ' + version, async () => {
console.debug(version)
await timedTest(version)
await sleep(100)
})
}
})

68
test/proxy.js Normal file
View file

@ -0,0 +1,68 @@
const { createClient, Server, Relay } = require('bedrock-protocol')
const { sleep, waitFor } = require('../src/datatypes/util')
const { getPort } = require('./util')
function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 40) {
console.log('with raknet backend', raknetBackend)
return waitFor(async res => {
const SERVER_PORT = await getPort()
const CLIENT_PORT = await getPort()
const server = new Server({
host: '0.0.0.0', // optional
port: SERVER_PORT, // optional
offline: true,
raknetBackend,
version // The server version
})
await server.listen()
server.on('connect', client => {
console.debug('Client has connected')
client.on('join', () => { // The client has joined the server.
console.debug('Client has authenticated')
setTimeout(() => {
client.disconnect('Hello world !')
}, 500) // allow some time for client to connect
})
})
console.debug('Server started', server.options.version)
await new Promise(resolve => setTimeout(resolve, 500))
const relay = new Relay({
version,
offline: true,
/* host and port for clients to listen to */
host: '0.0.0.0',
port: CLIENT_PORT,
/* Where to send upstream packets to */
destination: {
host: '127.0.0.1',
port: SERVER_PORT
},
raknetBackend
})
relay.conLog = console.debug
await relay.listen()
console.debug('Proxy started', server.options.version)
await new Promise(resolve => setTimeout(resolve, 500))
const client = createClient({ host: '127.0.0.1', port: CLIENT_PORT, version, username: 'Boat', offline: true, raknetBackend, skipPing: true })
console.debug('Client started')
client.on('error', console.log)
client.on('packet', console.log)
client.on('disconnect', packet => {
console.assert(packet.message === 'Hello world !')
server.close()
relay.close()
console.log('✔ OK')
sleep(200).then(res)
})
}, timeout, () => { throw Error('timed out') })
}
// if (!module.parent) { proxyTest('1.16.220', 'raknet-native') }
module.exports = { proxyTest }

18
test/proxy.test.js Normal file
View file

@ -0,0 +1,18 @@
/* eslint-env jest */
const { proxyTest } = require('./proxy')
const { testedVersions } = require('../src/options')
const { sleep } = require('../src/datatypes/util')
describe('proxies client/server', function () {
const vcount = testedVersions.length
this.timeout(vcount * 30 * 1000)
for (const version of testedVersions) {
it('proxies ' + version, async () => {
console.debug(version)
await proxyTest(version)
await sleep(100)
console.debug('Done', version)
})
}
})

17
test/util.js Normal file
View file

@ -0,0 +1,17 @@
const net = require('net')
const getPort = () => new Promise(resolve => {
const server = net.createServer()
server.listen(0, '127.0.0.1')
server.on('listening', () => {
const { port } = server.address()
server.close(() => {
// Wait a bit for port to free as we try to bind right after freeing it
setTimeout(() => {
resolve(port)
}, 200)
})
})
})
module.exports = { getPort }

74
test/vanilla.js Normal file
View file

@ -0,0 +1,74 @@
// process.env.DEBUG = 'minecraft-protocol raknet'
const vanillaServer = require('../tools/startVanillaServer')
const { Client } = require('../src/client')
const { waitFor } = require('../src/datatypes/util')
const { getPort } = require('./util')
async function test (version) {
// const ChunkColumn = require('bedrock-provider').chunk('bedrock_' + (version.includes('1.19') ? '1.18.30' : version)) // TODO: Fix prismarine-chunk
// Start the server, wait for it to accept clients, throws on timeout
const [port, v6] = [await getPort(), await getPort()]
console.log('Starting vanilla server', version, 'on port', port, v6)
const handle = await vanillaServer.startServerAndWait2(version, 1000 * 220, { 'server-port': port, 'server-portv6': v6 })
console.log('Started server')
const client = new Client({
host: '127.0.0.1',
port,
username: 'Notch',
version,
raknetBackend: 'raknet-native',
offline: true
})
console.log('Started client')
client.connect()
let loop
await waitFor((res) => {
client.once('resource_packs_info', (packet) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
client.once('resource_pack_stack', (stack) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
})
client.queue('client_cache_status', { enabled: false })
client.queue('request_chunk_radius', { chunk_radius: 1 })
clearInterval(loop)
loop = setInterval(() => {
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: BigInt(Date.now()) })
}, 200)
// client.on('level_chunk', async packet => { // Chunk read test
// const cc = new ChunkColumn(packet.x, packet.z)
// await cc.networkDecodeNoCache(packet.payload, packet.sub_chunk_count)
// })
console.log('Awaiting join')
client.on('spawn', () => {
console.log('✔ Client has spawned')
client.close()
handle.kill()
res()
})
})
}, 1000 * 60, () => {
client.close()
handle.kill()
throw Error('❌ client timed out ')
})
clearInterval(loop)
}
module.exports = { clientTest: test }

17
test/vanilla.test.js Normal file
View file

@ -0,0 +1,17 @@
/* eslint-env jest */
const { clientTest } = require('./vanilla')
const { testedVersions } = require('../src/options')
const { sleep } = require('../src/datatypes/util')
describe('vanilla server test', function () {
const vcount = testedVersions.length
this.timeout(vcount * 80 * 1000)
for (const version of testedVersions) {
it('client spawns ' + version, async () => {
await clientTest(version)
await sleep(100)
})
}
})

47
tools/compileProtocol.js Normal file
View file

@ -0,0 +1,47 @@
/**
* Pre-compiles JS code from the schema for easier development.
* You can run this with `npm run build`
*/
const fs = require('fs')
const { ProtoDefCompiler } = require('protodef').Compiler
const { convert } = require('minecraft-data/minecraft-data/tools/js/compileProtocol')
const mcData = require('minecraft-data')
const { join } = require('path')
// Filter versions we support
const versions = mcData.versions.bedrock.filter(e => e.releaseType === 'release').map(e => e.minecraftVersion)
// Compile the ProtoDef JSON into JS
function createProtocol (version) {
const compiler = new ProtoDefCompiler()
const protocol = mcData('bedrock_' + version).protocol.types
compiler.addTypes(require('../src/datatypes/compiler-minecraft'))
compiler.addTypesToCompile(protocol)
fs.writeFileSync('./read.js', 'module.exports = ' + compiler.readCompiler.generate().replace('() =>', 'native =>'))
fs.writeFileSync('./write.js', 'module.exports = ' + compiler.writeCompiler.generate().replace('() =>', 'native =>'))
fs.writeFileSync('./size.js', 'module.exports = ' + compiler.sizeOfCompiler.generate().replace('() =>', 'native =>'))
const compiledProto = compiler.compileProtoDefSync()
return compiledProto
}
function main (ver = 'latest') {
// Put the .js files into the data/ dir, we also use the data dir when dumping packets for tests
const dir = join(__dirname, '/../data/', ver)
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
process.chdir(dir)
console.log('Generating JS...', ver)
createProtocol(ver)
}
require('minecraft-data/bin/generate_data')
// If no argument, build everything
if (!process.argv[2]) {
convert('bedrock', 'latest')
for (const version of versions) {
main(version)
}
} else { // build the specified version
main(process.argv[2])
}

165
tools/dumpPackets.js Normal file
View file

@ -0,0 +1,165 @@
// dumps (up to 5 of each) packet encountered until 'spawn' event
// uses the same format as prismarine-packet-dumper
const assert = require('assert')
const fs = require('fs')
const vanillaServer = require('../tools/startVanillaServer')
const { Client } = require('../src/client')
const { serialize, waitFor } = require('../src/datatypes/util')
const { CURRENT_VERSION } = require('../src/options')
const path = require('path')
const output = path.resolve(process.argv[3] ?? 'output')
let loop
async function dump (version) {
const random = ((Math.random() * 100) | 0)
const port = 19130 + random
const handle = await vanillaServer.startServerAndWait(version || CURRENT_VERSION, 1000 * 120, { 'server-port': port })
console.log('Started server')
const client = new Client({
host: '127.0.0.1',
port,
username: 'dumpBot',
offline: true
})
return waitFor(async res => {
await fs.promises.mkdir(output)
await fs.promises.mkdir(path.join(output, 'from-server'))
client.once('resource_packs_info', (packet) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
client.once('resource_pack_stack', (stack) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
})
client.queue('client_cache_status', { enabled: false })
client.queue('request_chunk_radius', { chunk_radius: 1 })
clearInterval(loop)
loop = setInterval(() => {
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: BigInt(Date.now()) })
}, 200)
})
const kindCounter = {}
const MAX_PACKETS_PER_TYPE = 5
client.on('packet', async packet => { // Packet dumping
const { fullBuffer, data: { name, params } } = packet
if (!packet.data.name) return
if (!kindCounter[packet.name]) {
await fs.promises.mkdir(path.join(output, 'from-server', name), { recursive: true })
kindCounter[name] = 0
}
if (kindCounter[name] === MAX_PACKETS_PER_TYPE) return
kindCounter[name]++
await fs.promises.writeFile(path.join(output, 'from-server', name, `${kindCounter[name]}.bin`), fullBuffer)
try {
fs.writeFileSync(path.join(output, 'from-server', name, `${kindCounter[name]}.json`), serialize(params, 2))
} catch (e) {
console.log(e)
}
})
console.log('Awaiting join...')
client.on('spawn', () => {
console.log('Spawned!')
clearInterval(loop)
client.close()
handle.kill()
res(kindCounter)
})
}, 1000 * 60, () => {
clearInterval(loop)
handle.kill()
throw Error('timed out')
})
}
const makeDropdownStart = (name, arr) => {
arr.push(`<details><summary>${name}</summary>`)
arr.push('<p>')
arr.push('')
}
const makeDropdownEnd = (arr) => {
arr.push('')
arr.push('</p>')
arr.push('</details>')
}
function makeMarkdown (data) {
const str = []
const { collected, missing } = data
makeDropdownStart(`Collected (${collected.length})`, str)
str.push('| Packet |')
str.push('| --- |')
collected.forEach(elem => {
str.push(`| ${elem} |`)
})
makeDropdownEnd(str)
makeDropdownStart(`Missing (${missing.length})`, str)
str.push('| Packet |')
str.push('| --- |')
missing.forEach(elem => {
str.push(`| ${elem} |`)
})
makeDropdownEnd(str)
return str.join('\n')
}
function parsePacketCounter (version, kindCounter) {
const protocol = require(`../data/${version}/protocol.json`)
// record packets
return {
collectedPackets: Object.keys(kindCounter),
allPackets: Object.keys(protocol)
.filter(o => o.startsWith('packet_'))
.map(o => o.replace('packet_', ''))
}
}
async function makeStats (kindCounter, version) {
const { collectedPackets, allPackets } = parsePacketCounter(version, kindCounter)
// write packet data
const data = {
collected: collectedPackets,
missing: allPackets.filter(o => !collectedPackets.includes(o))
}
const metadataFolder = path.join(output, 'metadata')
await fs.promises.writeFile(path.join(output, 'README.md'), makeMarkdown(data))
await fs.promises.mkdir(metadataFolder)
await fs.promises.writeFile(path.join(metadataFolder, 'packets_info.json'), JSON.stringify(data, null, 2))
}
async function main () {
const version = process.argv[2]
if (!version) {
console.error('Usage: node dumpPackets.js <version> [outputPath]')
}
const vers = Object.keys(require('../src/options').Versions)
assert(vers.includes(version), 'Version not supported')
if (fs.existsSync(output)) fs.promises.rm(output, { force: true, recursive: true })
const kindCounter = await dump(version)
await fs.promises.rm(path.join(output, '..', `bds-${version}`), { recursive: true })
await makeStats(kindCounter, version)
console.log('Successfully dumped packets')
}
main()

107
tools/genPacketDumps.js Normal file
View file

@ -0,0 +1,107 @@
// Collect sample packets needed for `serverTest.js`
// process.env.DEBUG = 'minecraft-protocol'
const fs = require('fs')
const vanillaServer = require('../tools/startVanillaServer')
const { Client } = require('../src/client')
const { serialize, waitFor, getFiles } = require('../src/datatypes/util')
const { CURRENT_VERSION } = require('../src/options')
const { join } = require('path')
const { getPort } = require('../test/util')
function hasDumps (version) {
const root = join(__dirname, `../data/${version}/sample/packets/`)
if (!fs.existsSync(root) || getFiles(root).length < 10) {
return false
}
return true
}
let loop
async function dump (version, force = true) {
const random = (Math.random() * 1000) | 0
const [port, v6] = [await getPort(), await getPort()]
console.log('Starting dump server', version, 'on port', port, v6)
const handle = await vanillaServer.startServerAndWait2(version || CURRENT_VERSION, 1000 * 120, { 'server-port': port, 'server-portv6': v6 })
console.log('Started dump server', version)
const client = new Client({
host: '127.0.0.1',
port,
version,
username: 'Packet' + random,
offline: true
})
client.connect()
return waitFor(async res => {
const root = join(__dirname, `../data/${client.options.version}/sample/`)
fs.mkdirSync(root + 'packets', { recursive: true })
fs.mkdirSync(root + 'chunks', { recursive: true })
client.once('resource_packs_info', (packet) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
client.once('resource_pack_stack', (stack) => {
client.write('resource_pack_client_response', {
response_status: 'completed',
resourcepackids: []
})
})
client.queue('client_cache_status', { enabled: false })
client.queue('request_chunk_radius', { chunk_radius: 1 })
clearInterval(loop)
loop = setInterval(() => {
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: BigInt(Date.now()) })
}, 200)
})
let i = 0
client.on('packet', async packet => { // Packet dumping
const { name, params } = packet.data
if (name === 'level_chunk') {
fs.writeFileSync(root + `chunks/${name}-${i++}.bin`, packet.buffer)
return
}
try {
if (!fs.existsSync(root + `packets/${name}.json`) || force) {
fs.writeFileSync(root + `packets/${name}.json`, serialize(params, 2))
}
} catch (e) { console.log(e) }
})
console.log('Awaiting join...')
client.on('spawn', () => {
console.log('Spawned!')
clearInterval(loop)
client.close()
handle.kill()
res()
})
}, 1000 * 60, () => {
clearInterval(loop)
handle.kill()
throw Error('Timed out')
})
}
if (!module.parent) {
dump(null, true).then(() => {
console.log('Successfully dumped packets')
})
}
module.exports = { dumpPackets: dump, hasDumps }

8
tools/installPMMP.sh Normal file
View file

@ -0,0 +1,8 @@
rm -fr pmmp
mkdir pmmp && cd pmmp
wget https://github.com/pmmp/PHP-Binaries/releases/download/php-8.1-latest/PHP-Linux-x86_64-PM5.tar.gz
tar -xvf PHP-Linux-x86_64-PM5.tar.gz bin/
git clone https://github.com/pmmp/PocketMine-MP.git
cd PocketMine-MP
../bin/php7/bin/php /usr/bin/composer install
../bin/php7/bin/php src/PocketMine.php --no-wizard --xbox-auth=0 --settings.enable-dev-builds=1 --anonymous-statistics.enabled=0 --disable-readline --debug.level=2

View file

@ -0,0 +1,11 @@
const bedrockServer = require('minecraft-bedrock-server')
module.exports = {
...bedrockServer,
startServerAndWait (version, withTimeout, options) {
return bedrockServer.startServerAndWait(version, withTimeout, { ...options, root: __dirname })
},
startServerAndWait2 (version, withTimeout, options) {
return bedrockServer.startServerAndWait2(version, withTimeout, { ...options, root: __dirname })
}
}

67
types/Item.js Normal file
View file

@ -0,0 +1,67 @@
const { Versions } = require('../src/options')
module.exports = (version) =>
class Item {
nbt
constructor (obj) {
this.networkId = 0
this.runtimeId = 0
this.count = 0
this.metadata = 0
Object.assign(this, obj)
this.version = version
}
static fromBedrock (obj) {
if (Versions[version] >= Versions['1.16.220']) {
return new Item({
networkId: obj.network_id,
stackId: obj.stack_id,
blockRuntimeId: obj.block_runtime_id,
count: obj.count,
metadata: obj.metadata,
nbt: obj.extra.nbt
})
} else {
return new Item({
networkId: obj.runtime_id,
sackId: obj.item?.network_id,
count: obj.item?.auxiliary_value & 0xff,
metadata: obj.item?.auxiliary_value >> 8,
nbt: obj.item?.nbt?.nbt
})
}
}
toBedrock () {
if (Versions[version] >= Versions['1.16.220']) {
return {
network_id: this.networkId,
count: this.count,
metadata: this.metadata,
has_stack_id: this.stackId,
stack_id: this.stackId,
extra: {
has_nbt: !!this.nbt,
nbt: { version: 1, nbt: this.nbt },
can_place_on: [],
can_destroy: [],
blocking_tick: 0
}
}
} else {
return {
runtime_id: this.runtimeId,
item: {
network_id: this.networkId,
auxiliary_value: (this.metadata << 8) | (this.count & 0xff),
has_nbt: !!this.nbt,
nbt: { version: 1, nbt: this.nbt },
can_place_on: [],
can_destroy: [],
blocking_tick: 0
}
}
}
}
}