Compare commits
121 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa6acab0f3 |
||
|
|
b48518a6e7 |
||
|
|
407756b938 |
||
|
|
0b9c49fedc |
||
|
|
6b1474d2c6 |
||
|
|
6c659feb5d |
||
|
|
06fb3de3a0 |
||
|
|
6f06a8996e |
||
|
|
14daa2d95a |
||
|
|
80751d58a7 |
||
|
|
c66cdd3d62 |
||
|
|
e503c47c79 |
||
|
|
29ba39343a |
||
|
|
e71fd513dd |
||
|
|
b6b0bcdd70 |
||
|
|
d88309507d |
||
|
|
065f41db8c |
||
|
|
9f11f21991 |
||
|
|
47f342ca95 |
||
|
|
328785d8af |
||
|
|
d4a9faf153 |
||
|
|
eeb5e47e35 |
||
|
|
2d7d32dfd1 |
||
|
|
0ed8e32be8 |
||
|
|
75fa085e86 |
||
|
|
8e728255c7 |
||
|
|
cd9a31a435 |
||
|
|
38dc5a2561 |
||
|
|
a79be99c0f |
||
|
|
a37bc5054e |
||
|
|
e7bb990aea | ||
|
|
dfcf5b2f1b | ||
|
|
aa3dafa5f4 |
||
|
|
24d3200181 |
||
|
|
347e303ce4 |
||
|
|
f48b50849a |
||
|
|
1c0836bff0 |
||
|
|
b4adf04845 |
||
|
|
52358d7277 |
||
|
|
29c7b92d96 |
||
|
|
911e0e890f |
||
|
|
fa730786d5 |
||
|
|
dd5c4de4f2 |
||
|
|
a4023b987b |
||
|
|
cd937a45d0 |
||
|
|
391ea9814c |
||
|
|
fc30c96135 |
||
|
|
cb530c8b45 |
||
|
|
418efa1da6 |
||
|
|
3be55777fa |
||
|
|
4c3f62567e |
||
|
|
a797201ecb |
||
|
|
5d3986924d |
||
|
|
c4593aa355 |
||
|
|
84c5231b92 |
||
|
|
1fee54f517 |
||
|
|
5b2d78792c |
||
|
|
19ade45ea5 |
||
|
|
16e15d80a5 |
||
|
|
1ddc525169 |
||
|
|
a4346f9638 |
||
|
|
bd32aa8d04 |
||
|
|
c65fea2916 |
||
|
|
3e5158bebe |
||
|
|
a7ae528c16 |
||
|
|
184339b9d4 |
||
|
|
d8e707112a |
||
|
|
5ee5744ec9 |
||
|
|
914ed6840b |
||
|
|
ab93d0d082 |
||
|
|
0132302e53 |
||
|
|
c278a03f95 |
||
|
|
d3161badc6 |
||
|
|
be6f0cde9f |
||
|
|
842e66266f |
||
|
|
54840f818c |
||
|
|
7b74cbf712 |
||
|
|
fd165b8908 |
||
|
|
d53211c6a1 |
||
|
|
2ecf01d63e |
||
|
|
d66dc1b237 |
||
|
|
63eb673c1f |
||
|
|
50911d2463 |
||
|
|
689658c4ab |
||
|
|
755ba31ea7 |
||
|
|
22502b90fd |
||
|
|
f92db61c89 |
||
|
|
8f3b6c5aec |
||
|
|
4c7bb65ad0 |
||
|
|
f0f1351d40 |
||
|
|
2c00402a9e |
||
|
|
50cd489f6e |
||
|
|
1414420574 |
||
|
|
010d57e78a |
||
|
|
d063a6427a |
||
|
|
46f11204ba |
||
|
|
fe3c3b01fc |
||
|
|
b2c141c25f |
||
|
|
aa0b5743b5 |
||
|
|
d2c4430833 |
||
|
|
d31b2b2aec |
||
|
|
0b7f3e037d |
||
|
|
6d708339aa |
||
|
|
97b8140fdd |
||
|
|
79a6391709 |
||
|
|
cd0ddc16e1 |
||
|
|
2cea7d6912 |
||
|
|
3b59446780 |
||
|
|
17d5b15bac |
||
|
|
32e0c66997 |
||
|
|
8507286895 |
||
|
|
a8f1349c83 |
||
|
|
d96f1a1a69 |
||
|
|
d4226e0ffd |
||
|
|
55cac72af1 |
||
|
|
3c23add812 |
||
|
|
9cda78f9b5 |
||
|
|
92b5e42bb5 |
||
|
|
e5b8cc8bde |
||
|
|
27e85867a6 |
||
|
|
830a58144a |
44 changed files with 639 additions and 586 deletions
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
github: PrismarineJS
|
||||
open_collective: prismarinejs
|
||||
custom: https://rysolv.com/repos/detail/74691b23-938d-4b2f-b65a-5c47bf5b3f0f
|
||||
59
.github/helper-bot/github-helper.js
vendored
59
.github/helper-bot/github-helper.js
vendored
|
|
@ -1,59 +0,0 @@
|
|||
if (!process.env.CI) {
|
||||
// mock a bunch of things for testing locally -- https://github.com/actions/toolkit/issues/71
|
||||
process.env.GITHUB_REPOSITORY = 'PrismarineJS/bedrock-protocol'
|
||||
process.env.GITHUB_EVENT_NAME = 'issue_comment'
|
||||
process.env.GITHUB_SHA = 'cb2fd97b6eae9f2c7fee79d5a86eb9c3b4ac80d8'
|
||||
process.env.GITHUB_REF = 'refs/heads/master'
|
||||
process.env.GITHUB_WORKFLOW = 'Issue comments'
|
||||
process.env.GITHUB_ACTION = 'run1'
|
||||
process.env.GITHUB_ACTOR = 'test-user'
|
||||
module.exports = { getIssueStatus: () => ({}), updateIssue: () => {}, createIssue: () => {} }
|
||||
return
|
||||
}
|
||||
|
||||
// const { Octokit } = require('@octokit/rest') // https://github.com/octokit/rest.js
|
||||
const github = require('@actions/github')
|
||||
|
||||
const token = process.env.GITHUB_TOKEN
|
||||
const octokit = github.getOctokit(token)
|
||||
const context = github.context
|
||||
|
||||
async function getIssueStatus (title) {
|
||||
// https://docs.github.com/en/rest/reference/search#search-issues-and-pull-requests
|
||||
const existingIssues = await octokit.rest.search.issuesAndPullRequests({
|
||||
q: `is:issue repo:${process.env.GITHUB_REPOSITORY} in:title ${title}`
|
||||
})
|
||||
// console.log('Existing issues', existingIssues)
|
||||
const existingIssue = existingIssues.data.items.find(issue => issue.title === title)
|
||||
|
||||
if (!existingIssue) return {}
|
||||
|
||||
return { open: existingIssue.state === 'open', closed: existingIssue.state === 'closed', id: existingIssue.number }
|
||||
}
|
||||
|
||||
async function updateIssue (id, payload) {
|
||||
const issue = await octokit.rest.issues.update({
|
||||
...context.repo,
|
||||
issue_number: id,
|
||||
body: payload.body
|
||||
})
|
||||
console.log(`Updated issue ${issue.data.title}#${issue.data.number}: ${issue.data.html_url}`)
|
||||
}
|
||||
|
||||
async function createIssue (payload) {
|
||||
const issue = await octokit.rest.issues.create({
|
||||
...context.repo,
|
||||
...payload
|
||||
})
|
||||
console.log(`Created issue ${issue.data.title}#${issue.data.number}: ${issue.data.html_url}`)
|
||||
}
|
||||
|
||||
async function close (id, reason) {
|
||||
if (reason) await octokit.rest.issues.createComment({ ...context.repo, issue_number: id, body: reason })
|
||||
const issue = await octokit.rest.issues.update({ ...context.repo, issue_number: id, state: 'closed' })
|
||||
console.log(`Closed issue ${issue.data.title}#${issue.data.number}: ${issue.data.html_url}`)
|
||||
}
|
||||
|
||||
if (process.env.CI) {
|
||||
module.exports = { getIssueStatus, updateIssue, createIssue, close }
|
||||
}
|
||||
11
.github/helper-bot/index.js
vendored
11
.github/helper-bot/index.js
vendored
|
|
@ -1,7 +1,7 @@
|
|||
// Automatic version update checker for Minecraft bedrock edition.
|
||||
const fs = require('fs')
|
||||
const cp = require('child_process')
|
||||
const helper = require('./github-helper')
|
||||
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'
|
||||
|
||||
|
|
@ -102,11 +102,10 @@ async function fetchLatest () {
|
|||
console.log(version, currentVersionReleaseDate, releaseNotes)
|
||||
|
||||
const title = `Support Minecraft ${result.version}`
|
||||
|
||||
const issueStatus = await helper.getIssueStatus(title)
|
||||
const issueStatus = await helper.findIssue({ titleIncludes: title }) || {}
|
||||
|
||||
if (supportedVersions.includes(version)) {
|
||||
if (issueStatus.open) {
|
||||
if (issueStatus.isOpen) {
|
||||
helper.close(issueStatus.id, `Closing as ${version} is now supported`)
|
||||
}
|
||||
console.log('Latest version is supported.')
|
||||
|
|
@ -114,7 +113,7 @@ async function fetchLatest () {
|
|||
}
|
||||
|
||||
|
||||
if (issueStatus.closed) {
|
||||
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
|
||||
|
|
@ -127,7 +126,7 @@ async function fetchLatest () {
|
|||
CloudburstMC: getCommitsInRepo('CloudburstMC/Protocol', version, currentVersionReleaseDate)
|
||||
})
|
||||
|
||||
if (issueStatus.open) {
|
||||
if (issueStatus.isOpen) {
|
||||
helper.updateIssue(issueStatus.id, issuePayload)
|
||||
} else {
|
||||
helper.createIssue(issuePayload)
|
||||
|
|
|
|||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
|
@ -17,14 +17,20 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
node-version: [16.x]
|
||||
node-version: [22.x]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 12
|
||||
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
22
.github/workflows/commands.yml
vendored
Normal 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
|
||||
6
.github/workflows/update-helper.yml
vendored
6
.github/workflows/update-helper.yml
vendored
|
|
@ -14,9 +14,9 @@ jobs:
|
|||
- name: Set up Node.js
|
||||
uses: actions/setup-node@master
|
||||
with:
|
||||
node-version: 16.0.0
|
||||
- name: Install Github Actions toolkit
|
||||
run: npm i @actions/github
|
||||
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
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,4 +5,4 @@ __*
|
|||
# Runtime generated data
|
||||
data/
|
||||
tools/bds*
|
||||
*.txt
|
||||
tools/pmmp*
|
||||
134
HISTORY.md
134
HISTORY.md
|
|
@ -1,15 +1,141 @@
|
|||
# 3.26.0
|
||||
## 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
|
||||
## 3.25.0
|
||||
* 1.19.63 support (@stevarino)
|
||||
* Add close packet in server player API doc (#347) @hvlxh
|
||||
|
||||
# 3.24.0
|
||||
## 3.24.0
|
||||
* 1.19.62 support (@CreeperG16)
|
||||
|
||||
# 3.23.0
|
||||
## 3.23.0
|
||||
* 1.19.60 support (@CreeperG16)
|
||||
* added onMsaCode, profilesFolder to ClientOptions (@jarco-dev)
|
||||
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
[](https://discord.gg/GsEFRM8)
|
||||
|
||||
Minecraft Bedrock Edition (aka MCPE) protocol library, supporting authentication and encryption. Help [contribute](CONTRIBUTING.md).
|
||||
Minecraft Bedrock Edition (aka MCPE) protocol library, supporting authentication and encryption. Help [contribute](docs/CONTRIBUTING.md).
|
||||
|
||||
[Protocol doc](https://minecraft-data.prismarine.js.org/?v=bedrock_1.19.10&d=protocol)
|
||||
[Protocol doc](https://prismarinejs.github.io/minecraft-data/?v=bedrock_1.19.10&d=protocol)
|
||||
|
||||
## Features
|
||||
|
||||
- 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
|
||||
- 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)
|
||||
|
|
@ -34,6 +34,8 @@ Want to contribute on something important for PrismarineJS ? go to https://githu
|
|||
|
||||
`npm install bedrock-protocol`
|
||||
|
||||
To update bedrock-protocol (or any Node.js package) and its dependencies after a previous install, you must run `npm update --depth 9999`
|
||||
|
||||
## Usage
|
||||
|
||||
### Client example
|
||||
|
|
@ -52,7 +54,7 @@ const client = bedrock.createClient({
|
|||
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: '',
|
||||
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()}`
|
||||
})
|
||||
}
|
||||
|
|
@ -102,7 +104,7 @@ ping({ host: 'play.cubecraft.net', port: 19132 }).then(res => {
|
|||
|
||||
## Documentation
|
||||
|
||||
For documentation on the protocol, and packets/fields see the [protocol documentation](https://minecraft-data.prismarine.js.org/protocol/).
|
||||
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)
|
||||
|
||||
|
|
@ -126,7 +128,7 @@ Through node.js, add `process.env.DEBUG = 'minecraft-protocol'` at the top of yo
|
|||
|
||||
## Contribute
|
||||
|
||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md) and https://github.com/PrismarineJS/prismarine-contribute
|
||||
Please read [CONTRIBUTING.md](docs/CONTRIBUTING.md) and https://github.com/PrismarineJS/prismarine-contribute
|
||||
|
||||
## History
|
||||
|
||||
|
|
|
|||
18
docs/API.md
18
docs/API.md
|
|
@ -93,7 +93,7 @@ server.on('connect', (client) => {
|
|||
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://minecraft-data.prismarine.js.org/?v=bedrock_1.19.60&d=protocol#packet_text
|
||||
// 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!' })
|
||||
})
|
||||
})
|
||||
|
|
@ -137,12 +137,12 @@ client.on('text', (packet) => {
|
|||
console.log('Client got text packet', packet)
|
||||
})
|
||||
|
||||
// For example, we can listen to https://minecraft-data.prismarine.js.org/?v=bedrock_1.19.60&d=protocol#packet_add_player
|
||||
// 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: '',
|
||||
type: 'chat', needs_translation: false, source_name: client.username, xuid: '', platform_chat_id: '', filtered_message: '',
|
||||
message: `Hey, ${packet.username} just joined!`
|
||||
})
|
||||
})
|
||||
|
|
@ -164,7 +164,7 @@ You can use `.close()` to terminate a connection, and `.disconnect(reason)` to g
|
|||
|
||||
### Protocol docs
|
||||
|
||||
For documentation on the protocol, and packets/fields see the [the protocol doc](https://minecraft-data.prismarine.js.org/?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.
|
||||
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
|
||||
|
||||
|
|
@ -206,16 +206,22 @@ relay.on('connect', player => {
|
|||
console.log('New connection', player.connection.address)
|
||||
|
||||
// Server is sending a message to the client.
|
||||
player.on('clientbound', ({ name, params }) => {
|
||||
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 }) => {
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
|
|
|||
|
|
@ -110,37 +110,39 @@ The above roughly translates to the following JavaScript code to read a packet:
|
|||
```js
|
||||
function read_position(stream) {
|
||||
const ret = {}
|
||||
ret.x = stream.readSignedInt32LE()
|
||||
ret.z = stream.readUnsignedInt32LE()
|
||||
ret.y = stream.readFloat32LE()
|
||||
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_player_position(stream)
|
||||
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_player_position(stream)
|
||||
ret.jump_tick = stream.readInt64LE(stream)
|
||||
ret.original_position = read_position(stream)
|
||||
ret.jump_tick = stream.readLI64()
|
||||
break
|
||||
case 'player_fall':
|
||||
ret.original_position = read_player_position(stream)
|
||||
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 = []
|
||||
for (let i = 0; i < stream.readUnsignedVarInt(); i++) {
|
||||
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 = []
|
||||
for (let i = 0; i < stream.readZigZagVarInt(); i++) {
|
||||
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())
|
||||
17
docs/FAQ.md
17
docs/FAQ.md
|
|
@ -20,6 +20,23 @@ Get-AppxPackage -AllUsers | Where Name -Match ".*Minecraft.*" | Select Name,Inst
|
|||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const client = bedrock.createClient({
|
|||
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: '',
|
||||
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()}`
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const bedrock = require('bedrock-protocol')
|
|||
const server = bedrock.createServer({
|
||||
host: '0.0.0.0', // optional
|
||||
port: 19132, // optional
|
||||
version: '1.17.10', // The server version
|
||||
version: '1.19.80', // The server version
|
||||
motd: { // The message of the day
|
||||
motd: 'Funtime Server',
|
||||
levelName: 'Wonderland'
|
||||
|
|
@ -12,7 +12,7 @@ const server = bedrock.createServer({
|
|||
|
||||
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()} !`)
|
||||
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()} !`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ async function startServer (version = '1.17.10', ok) {
|
|||
must_accept: false,
|
||||
has_scripts: false,
|
||||
behaviour_packs: [],
|
||||
texture_packs: []
|
||||
texture_packs: [],
|
||||
resource_pack_links: []
|
||||
})
|
||||
|
||||
// ResourcePackStack is sent by the server to send the order in which resource packs and behaviour packs
|
||||
|
|
|
|||
113
index.d.ts
vendored
113
index.d.ts
vendored
|
|
@ -1,26 +1,24 @@
|
|||
import EventEmitter from "events"
|
||||
import { Realm } from "prismarine-realms"
|
||||
import { ServerDeviceCodeResponse } from "prismarine-auth"
|
||||
import EventEmitter from 'events'
|
||||
import { Realm } from 'prismarine-realms'
|
||||
import { ServerDeviceCodeResponse } from 'prismarine-auth'
|
||||
|
||||
declare module "bedrock-protocol" {
|
||||
type Version = '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'
|
||||
|
||||
enum title { MinecraftNintendoSwitch, MinecraftJava }
|
||||
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?: string
|
||||
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
|
||||
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,
|
||||
offline?: boolean
|
||||
|
||||
// Whether or not to use C++ version of RakNet
|
||||
useNativeRaknet?: 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
|
||||
|
|
@ -31,11 +29,11 @@ declare module "bedrock-protocol" {
|
|||
|
||||
export interface ClientOptions extends Options {
|
||||
// The username to connect to the server as
|
||||
username: string,
|
||||
username: string
|
||||
// The view distance in chunks
|
||||
viewDistance?: number,
|
||||
viewDistance?: number
|
||||
// Specifies which game edition to sign in as. Optional, but some servers verify this.
|
||||
authTitle?: title | string,
|
||||
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
|
||||
|
|
@ -49,31 +47,35 @@ declare module "bedrock-protocol" {
|
|||
// 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;
|
||||
onMsaCode?: (data: ServerDeviceCodeResponse) => void
|
||||
}
|
||||
|
||||
export interface ServerOptions extends Options {
|
||||
// The maximum number of players allowed on the server at any time.
|
||||
maxPlayers: number
|
||||
motd: {
|
||||
maxPlayers?: number
|
||||
motd?: {
|
||||
// The header for the MOTD shown in the server list.
|
||||
motd: string,
|
||||
motd: string
|
||||
// The sub-header for the MOTD shown in the server list.
|
||||
levelName: string
|
||||
levelName?: string
|
||||
}
|
||||
advertisementFn: () => ServerAdvertisement
|
||||
advertisementFn?: () => ServerAdvertisement
|
||||
}
|
||||
|
||||
enum ClientStatus {
|
||||
Disconected, Authenticating, Initializing, Initialized
|
||||
Disconnected,
|
||||
Authenticating,
|
||||
Initializing,
|
||||
Initialized
|
||||
}
|
||||
|
||||
export class Connection extends EventEmitter {
|
||||
readonly status: ClientStatus;
|
||||
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
|
||||
|
|
@ -100,7 +102,6 @@ declare module "bedrock-protocol" {
|
|||
// # 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
|
||||
|
|
@ -109,7 +110,7 @@ declare module "bedrock-protocol" {
|
|||
/**
|
||||
* Close the connection, leave the server.
|
||||
*/
|
||||
close(): void
|
||||
close(reason?: string): void
|
||||
|
||||
/**
|
||||
* Send a disconnect packet and close the connection
|
||||
|
|
@ -121,6 +122,15 @@ declare module "bedrock-protocol" {
|
|||
* `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
|
||||
|
|
@ -142,60 +152,73 @@ declare module "bedrock-protocol" {
|
|||
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>
|
||||
// Connection logging function
|
||||
conLog: Function
|
||||
|
||||
constructor(options: Options)
|
||||
// Disconnects all currently connected clients
|
||||
close(disconnectReason: string): void
|
||||
|
||||
listen(): Promise<void>
|
||||
close(disconnectReason?: string): Promise<void>
|
||||
|
||||
on(event: 'connect', cb: (client: Player) => void): any
|
||||
}
|
||||
|
||||
type RelayOptions = Options & {
|
||||
host: string,
|
||||
port: number,
|
||||
// Toggle packet logging.
|
||||
logging: boolean,
|
||||
logging?: boolean
|
||||
// Skip authentication for connecting clients?
|
||||
offline: false,
|
||||
offline?: false
|
||||
// Specifies which game edition to sign in as to the destination server. Optional, but some servers verify this.
|
||||
authTitle: title | string
|
||||
authTitle?: string
|
||||
// Where to proxy requests to.
|
||||
destination: {
|
||||
realms?: RealmsOptions
|
||||
host: string,
|
||||
port: number,
|
||||
host: string
|
||||
port: number
|
||||
// Skip authentication connecting to the remote server?
|
||||
offline: false,
|
||||
offline?: boolean
|
||||
}
|
||||
// Whether to enable chunk caching (default: false)
|
||||
enableChunkCaching?: boolean
|
||||
|
||||
// Only allow one client to connect at a time (default: false)
|
||||
forceSinge: boolean
|
||||
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
|
||||
onMsaCode?(data: ServerDeviceCodeResponse, client: Client): any
|
||||
// prismarine-auth configuration
|
||||
flow?: string,
|
||||
deviceType?: string
|
||||
}
|
||||
|
||||
export class Relay extends Server {
|
||||
constructor(options: RelayOptions)
|
||||
}
|
||||
|
||||
class ServerAdvertisement {
|
||||
export class ServerAdvertisement {
|
||||
motd: string
|
||||
name: string
|
||||
protocol: number
|
||||
version: string
|
||||
playersOnline: number
|
||||
playersMax: number
|
||||
gamemode: string
|
||||
serverId: string
|
||||
levelName:string
|
||||
levelName: string
|
||||
gamemodeId: number
|
||||
portV4: number
|
||||
portV6: number
|
||||
|
||||
constructor(obj: object, port: number, version: string)
|
||||
}
|
||||
|
||||
export interface RealmsOptions {
|
||||
|
|
@ -207,5 +230,11 @@ declare module "bedrock-protocol" {
|
|||
export function createClient(options: ClientOptions): Client
|
||||
export function createServer(options: ServerOptions): Server
|
||||
|
||||
export function ping({ host, port }: { host: string, port: number }): Promise<ServerAdvertisement>
|
||||
export function ping({
|
||||
host,
|
||||
port
|
||||
}: {
|
||||
host: string
|
||||
port: number
|
||||
}): Promise<ServerAdvertisement>
|
||||
}
|
||||
|
|
|
|||
4
index.js
4
index.js
|
|
@ -10,6 +10,7 @@ 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,
|
||||
|
|
@ -18,5 +19,6 @@ module.exports = {
|
|||
createClient,
|
||||
ping,
|
||||
createServer,
|
||||
title: Titles
|
||||
title: Titles,
|
||||
ServerAdvertisement
|
||||
}
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -1,14 +1,15 @@
|
|||
{
|
||||
"name": "bedrock-protocol",
|
||||
"version": "3.26.0",
|
||||
"version": "3.49.0",
|
||||
"description": "Minecraft Bedrock Edition protocol library",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"build": "cd tools && node compileProtocol.js",
|
||||
"test": "mocha --bail --exit",
|
||||
"test": "mocha --retries 2 --bail --exit",
|
||||
"pretest": "npm run lint",
|
||||
"lint": "standard",
|
||||
"vanillaServer": "node tools/startVanillaServer.js",
|
||||
"vanillaServer": "minecraft-bedrock-server --root tools --version",
|
||||
"dumpPackets": "node tools/genPacketDumps.js",
|
||||
"fix": "standard --fix"
|
||||
},
|
||||
|
|
@ -39,7 +40,8 @@
|
|||
"bedrock-protocol": "file:.",
|
||||
"bedrock-provider": "^2.0.0",
|
||||
"leveldb-zlib": "^1.0.1",
|
||||
"mocha": "^10.0.0",
|
||||
"minecraft-bedrock-server": "^1.4.2",
|
||||
"mocha": "^11.0.1",
|
||||
"protodef-yaml": "^1.1.0",
|
||||
"standard": "^17.0.0-2"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class Client extends Connection {
|
|||
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)
|
||||
|
|
@ -42,6 +43,7 @@ class Client extends Connection {
|
|||
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)
|
||||
|
|
@ -55,6 +57,19 @@ class Client extends Connection {
|
|||
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)
|
||||
|
|
@ -112,7 +127,7 @@ class Client extends Connection {
|
|||
this.connectTimeout = setTimeout(() => {
|
||||
if (this.status === ClientStatus.Disconnected) {
|
||||
this.connection.close()
|
||||
this.emit('error', 'connect timed out')
|
||||
this.emit('error', Error('Connect timed out'))
|
||||
}
|
||||
}, this.options.connectTimeout || 9000)
|
||||
}
|
||||
|
|
@ -120,6 +135,7 @@ class Client extends Connection {
|
|||
updateCompressorSettings (packet) {
|
||||
this.compressionAlgorithm = packet.compression_algorithm || 'deflate'
|
||||
this.compressionThreshold = packet.compression_threshold
|
||||
this.compressionReady = true
|
||||
}
|
||||
|
||||
sendLogin () {
|
||||
|
|
@ -131,9 +147,18 @@ class Client extends Connection {
|
|||
...this.accessToken // Mojang + Xbox JWT from auth
|
||||
]
|
||||
|
||||
const encodedChain = JSON.stringify({ chain })
|
||||
|
||||
debug('Auth chain', chain)
|
||||
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,
|
||||
|
|
@ -170,7 +195,8 @@ class Client extends Connection {
|
|||
if (this.status === ClientStatus.Disconnected) return
|
||||
this.write('disconnect', {
|
||||
hide_disconnect_screen: hide,
|
||||
message: reason
|
||||
message: reason,
|
||||
filtered_message: ''
|
||||
})
|
||||
this.close(reason)
|
||||
}
|
||||
|
|
@ -226,7 +252,9 @@ class Client extends Connection {
|
|||
break
|
||||
case 'start_game':
|
||||
this.startGameData = pakData.params
|
||||
this.startGameData.itemstates.forEach(state => {
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -28,17 +28,25 @@ class Connection extends EventEmitter {
|
|||
}
|
||||
|
||||
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)
|
||||
|
|
@ -62,10 +70,18 @@ class Connection extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
_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)
|
||||
if (name === 'start_game') this.updateItemPalette(params.itemstates)
|
||||
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
|
||||
this._processOutbound(name, params)
|
||||
const batch = new Framer(this)
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
batch.addEncodedPacket(packet)
|
||||
|
||||
|
|
@ -78,7 +94,7 @@ class Connection extends EventEmitter {
|
|||
|
||||
queue (name, params) {
|
||||
this.outLog?.('Q <- ', name, params)
|
||||
if (name === 'start_game') this.updateItemPalette(params.itemstates)
|
||||
this._processOutbound(name, params)
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
if (name === 'level_chunk') {
|
||||
// Skip queue, send ASAP
|
||||
|
|
@ -91,7 +107,7 @@ class Connection extends EventEmitter {
|
|||
|
||||
_tick () {
|
||||
if (this.sendQ.length) {
|
||||
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
|
||||
const batch = new Framer(this)
|
||||
batch.addEncodedPackets(this.sendQ)
|
||||
this.sendQ = []
|
||||
this.sendIds = []
|
||||
|
|
@ -115,7 +131,7 @@ class Connection extends EventEmitter {
|
|||
*/
|
||||
sendBuffer (buffer, immediate = false) {
|
||||
if (immediate) {
|
||||
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
|
||||
const batch = new Framer(this)
|
||||
batch.addEncodedPacket(buffer)
|
||||
if (this.encryptionEnabled) {
|
||||
this.sendEncryptedBatch(batch)
|
||||
|
|
@ -149,29 +165,29 @@ class Connection extends EventEmitter {
|
|||
|
||||
// These are callbacks called from encryption.js
|
||||
onEncryptedPacket = (buf) => {
|
||||
const packet = Buffer.concat([Buffer.from([0xfe]), buf]) // add header
|
||||
|
||||
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 (buffer[0] === 0xfe) { // wrapper
|
||||
if (!this.batchHeader || buffer[0] === this.batchHeader) { // wrapper
|
||||
if (this.encryptionEnabled) {
|
||||
this.decrypt(buffer.slice(1))
|
||||
} else {
|
||||
const packets = Framer.decode(this.compressionAlgorithm, buffer)
|
||||
const packets = Framer.decode(this, buffer)
|
||||
for (const packet of packets) {
|
||||
this.readPacket(packet)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw Error('Bad packet header ' + buffer[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ function createClient (options) {
|
|||
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)
|
||||
|
||||
if (ad.port && options.followPort) {
|
||||
client.options.port = ad.port
|
||||
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})` : ''}`)
|
||||
|
|
@ -56,35 +56,41 @@ function connect (client) {
|
|||
})
|
||||
|
||||
client.queue('client_cache_status', { enabled: false })
|
||||
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n })
|
||||
|
||||
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 }))
|
||||
})
|
||||
|
||||
// Send tick sync packets every 10 ticks
|
||||
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)
|
||||
if (client.versionLessThanOrEqualTo('1.20.80')) {
|
||||
const keepAliveInterval = 10
|
||||
const keepAliveIntervalBig = BigInt(keepAliveInterval)
|
||||
|
||||
client.on('tick_sync', async packet => {
|
||||
client.emit('heartbeat', packet.response_time)
|
||||
client.tick = packet.response_time
|
||||
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)
|
||||
})
|
||||
client.once('close', () => {
|
||||
clearInterval(keepalive)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function ping ({ host, port }) {
|
||||
const con = new RakClient({ host, port })
|
||||
|
||||
try {
|
||||
return advertisement.fromServerName(await con.ping())
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable */
|
||||
const UUID = require('uuid-1345')
|
||||
const minecraft = require('./minecraft')
|
||||
const { Read, Write, SizeOf } = require('./varlong')
|
||||
const [Read, Write, SizeOf] = [{}, {}, {}]
|
||||
|
||||
/**
|
||||
* UUIDs
|
||||
|
|
@ -116,74 +116,6 @@ Read.lnbt = ['native', minecraft.lnbt[0]]
|
|||
Write.lnbt = ['native', minecraft.lnbt[1]]
|
||||
SizeOf.lnbt = ['native', minecraft.lnbt[2]]
|
||||
|
||||
/**
|
||||
* Bits
|
||||
*/
|
||||
|
||||
Read.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) => {
|
||||
let fstr = JSON.stringify(flags)
|
||||
if (Array.isArray(flags)) {
|
||||
fstr = '{'
|
||||
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
|
||||
fstr += '}'
|
||||
} else if (shift) {
|
||||
fstr = '{'
|
||||
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`;
|
||||
fstr += '}'
|
||||
}
|
||||
return compiler.wrapCode(`
|
||||
const { value: _value, size } = ${compiler.callType(type, 'offset')}
|
||||
const value = { _value }
|
||||
const flags = ${fstr}
|
||||
for (const key in flags) {
|
||||
value[key] = (_value & flags[key]) == flags[key]
|
||||
}
|
||||
return { value, size }
|
||||
`.trim())
|
||||
}]
|
||||
|
||||
Write.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) => {
|
||||
let fstr = JSON.stringify(flags)
|
||||
if (Array.isArray(flags)) {
|
||||
fstr = '{'
|
||||
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
|
||||
fstr += '}'
|
||||
} else if (shift) {
|
||||
fstr = '{'
|
||||
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`;
|
||||
fstr += '}'
|
||||
}
|
||||
return compiler.wrapCode(`
|
||||
const flags = ${fstr}
|
||||
let val = value._value ${big ? '|| 0n' : ''}
|
||||
for (const key in flags) {
|
||||
if (value[key]) val |= flags[key]
|
||||
}
|
||||
return (ctx.${type})(val, buffer, offset)
|
||||
`.trim())
|
||||
}]
|
||||
|
||||
SizeOf.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) => {
|
||||
let fstr = JSON.stringify(flags)
|
||||
if (Array.isArray(flags)) {
|
||||
fstr = '{'
|
||||
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
|
||||
fstr += '}'
|
||||
} else if (shift) {
|
||||
fstr = '{'
|
||||
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`;
|
||||
fstr += '}'
|
||||
}
|
||||
return compiler.wrapCode(`
|
||||
const flags = ${fstr}
|
||||
let val = value._value ${big ? '|| 0n' : ''}
|
||||
for (const key in flags) {
|
||||
if (value[key]) val |= flags[key]
|
||||
}
|
||||
return (ctx.${type})(val)
|
||||
`.trim())
|
||||
}]
|
||||
|
||||
/**
|
||||
* Command Packet
|
||||
* - used for determining the size of the following enum
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
/* eslint-disable */
|
||||
const nbt = require('prismarine-nbt')
|
||||
const UUID = require('uuid-1345')
|
||||
|
||||
const protoLE = nbt.protos.little
|
||||
const protoLEV = nbt.protos.littleVarint
|
||||
// TODO: deal with this:
|
||||
const zigzag = require('prismarine-nbt/compiler-zigzag')
|
||||
|
||||
function readUUID (buffer, offset) {
|
||||
if (offset + 16 > buffer.length) { throw new PartialReadError() }
|
||||
if (offset + 16 > buffer.length) { throw new Error('Reached end of buffer') }
|
||||
return {
|
||||
value: UUID.stringify(buffer.slice(offset, 16 + offset)),
|
||||
size: 16
|
||||
|
|
@ -65,7 +62,7 @@ function readEntityMetadata (buffer, offset, _ref) {
|
|||
const metadata = []
|
||||
let item
|
||||
while (true) {
|
||||
if (offset + 1 > buffer.length) throw new PartialReadError()
|
||||
if (offset + 1 > buffer.length) throw new Error('Reached end of buffer')
|
||||
item = buffer.readUInt8(cursor)
|
||||
if (item === endVal) {
|
||||
return {
|
||||
|
|
@ -159,7 +156,5 @@ module.exports = {
|
|||
lnbt: [readNbtLE, writeNbtLE, sizeOfNbtLE],
|
||||
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
|
||||
ipAddress: [readIpAddress, writeIpAddress, 4],
|
||||
endOfArray: [readEndOfArray, writeEndOfArray, sizeOfEndOfArray],
|
||||
zigzag32: zigzag.zigzag32,
|
||||
zigzag64: zigzag.zigzag64
|
||||
endOfArray: [readEndOfArray, writeEndOfArray, sizeOfEndOfArray]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
function sizeOfVarLong (value) {
|
||||
if (typeof value.valueOf() === 'object') {
|
||||
value = (BigInt(value[0]) << 32n) | BigInt(value[1])
|
||||
} else if (typeof value !== 'bigint') value = BigInt(value)
|
||||
|
||||
let cursor = 0
|
||||
while (value > 127n) {
|
||||
value >>= 7n
|
||||
cursor++
|
||||
}
|
||||
return cursor + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 64-bit VarInt as a BigInt
|
||||
*/
|
||||
function readVarLong (buffer, offset) {
|
||||
let result = BigInt(0)
|
||||
let shift = 0n
|
||||
let cursor = offset
|
||||
let size = 0
|
||||
|
||||
while (true) {
|
||||
if (cursor + 1 > buffer.length) { throw new Error('unexpected buffer end') }
|
||||
const b = buffer.readUInt8(cursor)
|
||||
result |= (BigInt(b) & 0x7fn) << shift // Add the bits to our number, except MSB
|
||||
cursor++
|
||||
if (!(b & 0x80)) { // If the MSB is not set, we return the number
|
||||
size = cursor - offset
|
||||
break
|
||||
}
|
||||
shift += 7n // we only have 7 bits, MSB being the return-trigger
|
||||
if (shift > 63n) throw new Error(`varint is too big: ${shift}`)
|
||||
}
|
||||
|
||||
return { value: result, size }
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a zigzag encoded 64-bit VarInt as a BigInt
|
||||
*/
|
||||
function writeVarLong (value, buffer, offset) {
|
||||
// if an array, turn it into a BigInt
|
||||
if (typeof value.valueOf() === 'object') {
|
||||
value = BigInt.asIntN(64, (BigInt(value[0]) << 32n)) | BigInt(value[1])
|
||||
} else if (typeof value !== 'bigint') value = BigInt(value)
|
||||
|
||||
let cursor = 0
|
||||
while (value > 127n) { // keep writing in 7 bit slices
|
||||
const num = Number(value & 0xFFn)
|
||||
buffer.writeUInt8(num | 0x80, offset + cursor)
|
||||
cursor++
|
||||
value >>= 7n
|
||||
}
|
||||
buffer.writeUInt8(Number(value), offset + cursor)
|
||||
return offset + cursor + 1
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Read: { varint64: ['native', readVarLong] },
|
||||
Write: { varint64: ['native', writeVarLong] },
|
||||
SizeOf: { varint64: ['native', sizeOfVarLong] }
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
module.exports = {
|
||||
PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V'
|
||||
PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ module.exports = (client, server, options) => {
|
|||
client.createClientUserChain = (privateKey) => {
|
||||
let payload = {
|
||||
...skinData,
|
||||
SkinGeometryDataEngineVersion: '', // 1.17.30
|
||||
|
||||
ClientRandomId: Date.now(),
|
||||
CurrentInputMode: 1,
|
||||
|
|
@ -47,23 +46,30 @@ module.exports = (client, server, options) => {
|
|||
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.
|
||||
PlayFabId: nextUUID().replace(/-/g, '').slice(0, 16), // 1.16.210
|
||||
// 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,
|
||||
ThirdPartyNameOnly: false,
|
||||
ThirdPartyName: client.profile.name, // Gamertag
|
||||
ThirdPartyNameOnly: client.versionGreaterThanOrEqualTo('1.21.90') ? undefined : false,
|
||||
UIProfile: 0,
|
||||
|
||||
IsEditorMode: false,
|
||||
TrustedSkin: 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 }
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ 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.19.70'
|
||||
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']
|
||||
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.gamepedia.com/Protocol_version#Bedrock_Edition_2
|
||||
// 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
|
||||
|
|
|
|||
43
src/rak.js
43
src/rak.js
|
|
@ -5,22 +5,32 @@ const { waitFor } = require('./datatypes/util')
|
|||
let Client, Server, PacketPriority, EncapsulatedPacket, PacketReliability, Reliability
|
||||
class RakTimeout extends Error {};
|
||||
|
||||
module.exports = (backend) => {
|
||||
try {
|
||||
if (backend === 'jsp-raknet') {
|
||||
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 }
|
||||
}
|
||||
// We need to explicitly name the require()s for bundlers
|
||||
if (backend === 'raknet-node') ({ Client, Server, PacketPriority, PacketReliability } = require('raknet-node'))
|
||||
if (backend === 'raknet-native') ({ Client, Server, PacketPriority, PacketReliability } = require('raknet-native'))
|
||||
else ({ Client, Server, PacketPriority, PacketReliability } = require(backend))
|
||||
return { RakServer: RakNativeServer, RakClient: RakNativeClient, RakTimeout }
|
||||
} catch (e) {
|
||||
({ Client, Server, EncapsulatedPacket, Reliability } = require('jsp-raknet'))
|
||||
console.debug('[raknet] ' + backend + ' library not found, defaulting to jsp-raknet. Correct the "raknetBackend" option to avoid this error.', e)
|
||||
}
|
||||
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 {
|
||||
|
|
@ -58,7 +68,12 @@ class RakNativeClient extends EventEmitter {
|
|||
done(ret.extra.toString())
|
||||
}
|
||||
})
|
||||
}, timeout, () => { throw new RakTimeout('Ping timed out') })
|
||||
}, 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 () {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@ class RelayPlayer extends Player {
|
|||
} catch (e) {
|
||||
this.server.deserializer.dumpFailedBuffer(packet, this.connection.address)
|
||||
console.error(this.connection.address, e)
|
||||
this.disconnect('Server packet parse error')
|
||||
|
||||
if (!this.options.omitParseErrors) {
|
||||
this.disconnect('Server packet parse error')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
const name = des.data.name
|
||||
|
|
@ -182,12 +186,15 @@ class Relay extends Server {
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ const debug = globalThis.isElectron ? console.debug : require('debug')('minecraf
|
|||
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)
|
||||
|
|
@ -22,24 +24,44 @@ class Server extends EventEmitter {
|
|||
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) {
|
||||
if (algorithm === 'none') {
|
||||
this.compressionAlgorithm = 'none'
|
||||
this.compressionLevel = 0
|
||||
} else if (algorithm === 'deflate') {
|
||||
this.compressionAlgorithm = 'deflate'
|
||||
this.compressionLevel = level
|
||||
this.compressionThreshold = threshold
|
||||
} else if (algorithm === 'snappy') {
|
||||
this.compressionAlgorithm = 'snappy'
|
||||
this.compressionLevel = level
|
||||
this.compressionThreshold = threshold
|
||||
} else {
|
||||
throw new Error(`Unknown compression algorithm ${algorithm}`)
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,18 +82,18 @@ class Server extends EventEmitter {
|
|||
}
|
||||
|
||||
onOpenConnection = (conn) => {
|
||||
this.conLog('new connection', conn?.address)
|
||||
this.conLog('New connection: ', conn?.address)
|
||||
|
||||
const player = new Player(this, conn)
|
||||
this.clients[conn.address] = player
|
||||
this.clientCount++
|
||||
this.emit('connect', player)
|
||||
}
|
||||
|
||||
onCloseConnection = (inetAddr, reason) => {
|
||||
this.conLog('close connection', inetAddr?.address, reason)
|
||||
delete this.clients[inetAddr]?.connection // Prevent close loop
|
||||
this.clients[inetAddr?.address ?? inetAddr]?.close()
|
||||
delete this.clients[inetAddr]
|
||||
onCloseConnection = (conn, reason) => {
|
||||
this.conLog('Connection closed: ', conn.address, reason)
|
||||
this.clients[conn.address]?.close()
|
||||
delete this.clients[conn.address]
|
||||
this.clientCount--
|
||||
}
|
||||
|
||||
|
|
@ -79,9 +101,10 @@ class Server extends EventEmitter {
|
|||
const client = this.clients[address]
|
||||
if (!client) {
|
||||
// Ignore packets from clients that are not connected.
|
||||
debug(`ignoring packet from unknown inet addr: ${address}`)
|
||||
debug(`Ignoring packet from unknown inet address: ${address}`)
|
||||
return
|
||||
}
|
||||
|
||||
process.nextTick(() => client.handle(buffer))
|
||||
}
|
||||
|
||||
|
|
@ -89,18 +112,22 @@ class Server extends EventEmitter {
|
|||
if (this.options.advertisementFn) {
|
||||
return this.options.advertisementFn()
|
||||
}
|
||||
|
||||
this.advertisement.playersOnline = this.clientCount
|
||||
return this.advertisement
|
||||
}
|
||||
|
||||
async listen (host = this.options.host, port = this.options.port) {
|
||||
this.raknet = new this.RakServer({ host, port }, this)
|
||||
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
|
||||
|
|
@ -114,7 +141,7 @@ class Server extends EventEmitter {
|
|||
return { host, port }
|
||||
}
|
||||
|
||||
async close (disconnectReason) {
|
||||
async close (disconnectReason = 'Server closed') {
|
||||
for (const caddr in this.clients) {
|
||||
const client = this.clients[caddr]
|
||||
client.disconnect(disconnectReason)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ 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
|
||||
|
|
@ -23,14 +24,16 @@ class Player extends Connection {
|
|||
this.status = ClientStatus.Authenticating
|
||||
|
||||
if (isDebug) {
|
||||
this.inLog = (...args) => debug('S ->', ...args)
|
||||
this.outLog = (...args) => debug('S <-', ...args)
|
||||
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+
|
||||
}
|
||||
|
|
@ -48,6 +51,7 @@ class Player extends Connection {
|
|||
client_throttle_scalar: 0
|
||||
})
|
||||
this._sentNetworkSettings = true
|
||||
this.compressionReady = true
|
||||
}
|
||||
|
||||
handleClientProtocolVersion (clientVersion) {
|
||||
|
|
@ -74,11 +78,18 @@ class Player extends Connection {
|
|||
|
||||
// Parse login data
|
||||
const tokens = body.params.tokens
|
||||
const authChain = JSON.parse(tokens.identity)
|
||||
const skinChain = tokens.client
|
||||
|
||||
try {
|
||||
var { key, userData, skinData } = this.decodeLoginJWT(authChain.chain, skinChain) // eslint-disable-line
|
||||
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')
|
||||
|
|
@ -115,7 +126,8 @@ class Player extends Connection {
|
|||
if (this.status === ClientStatus.Disconnected) return
|
||||
this.write('disconnect', {
|
||||
hide_disconnect_screen: hide,
|
||||
message: reason
|
||||
message: reason,
|
||||
filtered_message: ''
|
||||
})
|
||||
this.server.conLog('Kicked ', this.connection?.address, reason)
|
||||
setTimeout(() => this.close('kick'), 100) // Allow time for message to be recieved.
|
||||
|
|
@ -152,7 +164,7 @@ class Player extends Connection {
|
|||
return
|
||||
}
|
||||
|
||||
this.inLog?.(des.data.name, serialize(des.data.params).slice(0, 200))
|
||||
this.inLog?.(des.data.name, serialize(des.data.params))
|
||||
|
||||
switch (des.data.name) {
|
||||
// This is the first packet on 1.19.30 & above
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ function createEncryptor (client, iv) {
|
|||
// The send counter is represented as a little-endian 64-bit long and incremented after each packet.
|
||||
|
||||
function process (chunk) {
|
||||
const buffer = Zlib.deflateRawSync(chunk, { level: client.compressionLevel })
|
||||
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)
|
||||
|
|
@ -70,7 +73,22 @@ function createDecryptor (client, iv) {
|
|||
return
|
||||
}
|
||||
|
||||
const buffer = Zlib.inflateRawSync(chunk, { chunkSize: 512000 })
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ const zlib = require('zlib')
|
|||
|
||||
// Concatenates packets into one batch packet, and adds length prefixs.
|
||||
class Framer {
|
||||
constructor (compressor, compressionLevel, compressionThreshold) {
|
||||
constructor (client) {
|
||||
// Encoding
|
||||
this.packets = []
|
||||
this.compressor = compressor || 'none'
|
||||
this.compressionLevel = compressionLevel
|
||||
this.compressionThreshold = compressionThreshold
|
||||
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
|
||||
|
|
@ -21,30 +24,46 @@ class Framer {
|
|||
}
|
||||
|
||||
static decompress (algorithm, buffer) {
|
||||
try {
|
||||
switch (algorithm) {
|
||||
case 'deflate': return zlib.inflateRawSync(buffer, { chunkSize: 512000 })
|
||||
case 'snappy': throw Error('Snappy compression not implemented')
|
||||
case 'none': return buffer
|
||||
default: throw Error('Unknown compression type ' + this.compressor)
|
||||
}
|
||||
} catch {
|
||||
return 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 (compressor, buf) {
|
||||
static decode (client, buf) {
|
||||
// Read header
|
||||
if (buf[0] !== 0xfe) throw Error('bad batch packet header ' + buf[0])
|
||||
if (this.batchHeader && buf[0] !== this.batchHeader) throw Error(`bad batch packet header, received: ${buf[0]}, expected: ${this.batchHeader}`)
|
||||
const buffer = buf.slice(1)
|
||||
const decompressed = this.decompress(compressor, buffer)
|
||||
// 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 compressed = (buf.length > this.compressionThreshold) ? this.compress(buf) : buf
|
||||
return Buffer.concat([Buffer.from([0xfe]), compressed])
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ function createProtocol (version) {
|
|||
const compiler = new ProtoDefCompiler()
|
||||
compiler.addTypesToCompile(protocol.types)
|
||||
compiler.addTypes(require('../datatypes/compiler-minecraft'))
|
||||
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
|
||||
|
||||
const compiledProto = compiler.compileProtoDefSync()
|
||||
return compiledProto
|
||||
|
|
@ -47,7 +46,6 @@ function createProtocol (version) {
|
|||
function getProtocol (version) {
|
||||
const compiler = new ProtoDefCompiler()
|
||||
compiler.addTypes(require(join(__dirname, '../datatypes/compiler-minecraft')))
|
||||
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
|
||||
|
||||
global.PartialReadError = require('protodef/src/utils').PartialReadError
|
||||
const compile = (compiler, file) => require(file)(compiler.native)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ function prepare (version) {
|
|||
|
||||
async function startTest (version = CURRENT_VERSION, ok) {
|
||||
await prepare(version)
|
||||
const Item = require('../types/Item')(version)
|
||||
// const Item = require('../types/Item')(version)
|
||||
const port = await getPort()
|
||||
const server = new Server({ host: '0.0.0.0', port, version, offline: true })
|
||||
|
||||
|
|
@ -47,7 +47,9 @@ async function startTest (version = CURRENT_VERSION, ok) {
|
|||
must_accept: false,
|
||||
has_scripts: false,
|
||||
behaviour_packs: [],
|
||||
texture_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 => {
|
||||
|
|
@ -55,13 +57,17 @@ async function startTest (version = CURRENT_VERSION, ok) {
|
|||
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_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'))
|
||||
client.queue('item_component', { entries: [] })
|
||||
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 })
|
||||
|
|
@ -95,11 +101,11 @@ async function startTest (version = CURRENT_VERSION, ok) {
|
|||
|
||||
loop = setInterval(() => {
|
||||
client.write('network_chunk_publisher_update', { coordinates: { x: 646, y: 130, z: 77 }, radius: 64 })
|
||||
}, 9500)
|
||||
}, 6500)
|
||||
|
||||
setTimeout(() => {
|
||||
client.write('play_status', { status: 'player_spawn' })
|
||||
}, 6000)
|
||||
}, 3000)
|
||||
|
||||
// Respond to tick synchronization packets
|
||||
client.on('tick_sync', (packet) => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
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
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4
|
|||
console.debug('Client has authenticated')
|
||||
setTimeout(() => {
|
||||
client.disconnect('Hello world !')
|
||||
}, 1000) // allow some time for client to connect
|
||||
}, 500) // allow some time for client to connect
|
||||
})
|
||||
})
|
||||
|
||||
console.debug('Server started', server.options.version)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
const relay = new Relay({
|
||||
version,
|
||||
|
|
@ -46,7 +46,7 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4
|
|||
await relay.listen()
|
||||
|
||||
console.debug('Proxy started', server.options.version)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
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')
|
||||
|
|
@ -58,7 +58,7 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4
|
|||
server.close()
|
||||
relay.close()
|
||||
console.log('✔ OK')
|
||||
sleep(500).then(res)
|
||||
sleep(200).then(res)
|
||||
})
|
||||
}, timeout, () => { throw Error('timed out') })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ describe('proxies client/server', function () {
|
|||
it('proxies ' + version, async () => {
|
||||
console.debug(version)
|
||||
await proxyTest(version)
|
||||
await sleep(1000)
|
||||
await sleep(100)
|
||||
console.debug('Done', version)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ const getPort = () => new Promise(resolve => {
|
|||
server.listen(0, '127.0.0.1')
|
||||
server.on('listening', () => {
|
||||
const { port } = server.address()
|
||||
server.close(() => resolve(port))
|
||||
server.close(() => {
|
||||
// Wait a bit for port to free as we try to bind right after freeing it
|
||||
setTimeout(() => {
|
||||
resolve(port)
|
||||
}, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ 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
|
||||
// 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 = await getPort()
|
||||
const handle = await vanillaServer.startServerAndWait2(version, 1000 * 220, { 'server-port': port })
|
||||
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({
|
||||
|
|
@ -48,10 +49,10 @@ async function test (version) {
|
|||
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)
|
||||
})
|
||||
// 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')
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ function createProtocol (version) {
|
|||
const compiler = new ProtoDefCompiler()
|
||||
const protocol = mcData('bedrock_' + version).protocol.types
|
||||
compiler.addTypes(require('../src/datatypes/compiler-minecraft'))
|
||||
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
|
||||
compiler.addTypesToCompile(protocol)
|
||||
|
||||
fs.writeFileSync('./read.js', 'module.exports = ' + compiler.readCompiler.generate().replace('() =>', 'native =>'))
|
||||
|
|
@ -39,7 +38,7 @@ require('minecraft-data/bin/generate_data')
|
|||
|
||||
// If no argument, build everything
|
||||
if (!process.argv[2]) {
|
||||
convert('latest')
|
||||
convert('bedrock', 'latest')
|
||||
for (const version of versions) {
|
||||
main(version)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ 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
|
||||
}
|
||||
|
||||
|
|
@ -22,7 +24,7 @@ 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)
|
||||
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)
|
||||
|
|
@ -30,16 +32,15 @@ async function dump (version, force = true) {
|
|||
host: '127.0.0.1',
|
||||
port,
|
||||
version,
|
||||
username: 'Boat' + random,
|
||||
username: 'Packet' + random,
|
||||
offline: true
|
||||
})
|
||||
client.connect()
|
||||
return waitFor(async res => {
|
||||
const root = join(__dirname, `../data/${client.options.version}/sample/`)
|
||||
if (!fs.existsSync(root + 'packets') || !fs.existsSync(root + 'chunks')) {
|
||||
fs.mkdirSync(root + 'packets', { recursive: true })
|
||||
fs.mkdirSync(root + 'chunks', { recursive: true })
|
||||
}
|
||||
|
||||
fs.mkdirSync(root + 'packets', { recursive: true })
|
||||
fs.mkdirSync(root + 'chunks', { recursive: true })
|
||||
|
||||
client.once('resource_packs_info', (packet) => {
|
||||
client.write('resource_pack_client_response', {
|
||||
|
|
@ -56,9 +57,9 @@ async function dump (version, force = true) {
|
|||
|
||||
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 })
|
||||
|
||||
clearInterval(loop)
|
||||
|
||||
loop = setInterval(() => {
|
||||
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: BigInt(Date.now()) })
|
||||
}, 200)
|
||||
|
|
@ -68,10 +69,12 @@ async function dump (version, force = true) {
|
|||
|
||||
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))
|
||||
|
|
@ -83,6 +86,7 @@ async function dump (version, force = true) {
|
|||
|
||||
client.on('spawn', () => {
|
||||
console.log('Spawned!')
|
||||
|
||||
clearInterval(loop)
|
||||
client.close()
|
||||
handle.kill()
|
||||
|
|
@ -91,7 +95,7 @@ async function dump (version, force = true) {
|
|||
}, 1000 * 60, () => {
|
||||
clearInterval(loop)
|
||||
handle.kill()
|
||||
throw Error('timed out')
|
||||
throw Error('Timed out')
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
8
tools/installPMMP.sh
Normal file
8
tools/installPMMP.sh
Normal 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
|
||||
|
|
@ -1,156 +1,11 @@
|
|||
const http = require('https')
|
||||
const fs = require('fs')
|
||||
const cp = require('child_process')
|
||||
const debug = process.env.CI ? console.debug : require('debug')('minecraft-protocol')
|
||||
const https = require('https')
|
||||
const { getFiles, waitFor } = require('../src/datatypes/util')
|
||||
const bedrockServer = require('minecraft-bedrock-server')
|
||||
|
||||
function head (url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(url, { method: 'HEAD', timeout: 500 }, resolve)
|
||||
req.on('error', reject)
|
||||
req.on('timeout', () => { req.destroy(); debug('HEAD request timeout'); reject(new Error('timeout')) })
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
function get (url, outPath) {
|
||||
const file = fs.createWriteStream(outPath)
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, { timeout: 1000 * 20 }, response => {
|
||||
if (response.statusCode !== 200) return reject(new Error('Server returned code ' + response.statusCode))
|
||||
response.pipe(file)
|
||||
file.on('finish', () => {
|
||||
file.close()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Get the latest versions
|
||||
// TODO: once we support multi-versions
|
||||
function fetchLatestStable () {
|
||||
get('https://raw.githubusercontent.com/minecraft-linux/mcpelauncher-versiondb/master/versions.json', 'versions.json')
|
||||
const versions = JSON.parse(fs.readFileSync('./versions.json'))
|
||||
const latest = versions[0]
|
||||
return latest.version_name
|
||||
}
|
||||
|
||||
// Download + extract vanilla server and enter the directory
|
||||
async function download (os, version, path = 'bds-') {
|
||||
debug('Downloading server', os, version, 'into', path)
|
||||
process.chdir(__dirname)
|
||||
const verStr = version.split('.').slice(0, 3).join('.')
|
||||
const dir = path + version
|
||||
|
||||
if (fs.existsSync(dir) && getFiles(dir).length) {
|
||||
process.chdir(path + version) // Enter server folder
|
||||
return verStr
|
||||
}
|
||||
try { fs.mkdirSync(dir) } catch { }
|
||||
|
||||
process.chdir(path + version) // Enter server folder
|
||||
const url = (os, version) => `https://minecraft.azureedge.net/bin-${os}/bedrock-server-${version}.zip`
|
||||
|
||||
let found = false
|
||||
|
||||
for (let i = 0; i < 8; i++) { // Check for the latest server build for version (major.minor.patch.BUILD)
|
||||
const u = url(os, `${verStr}.${String(i).padStart(2, '0')}`)
|
||||
debug('Opening', u, Date.now())
|
||||
let ret
|
||||
try { ret = await head(u) } catch (e) { continue }
|
||||
if (ret.statusCode === 200) {
|
||||
found = u
|
||||
debug('Found server', ret.statusCode)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!found) throw Error('did not find server bin for ' + os + ' ' + version)
|
||||
console.info('🔻 Downloading', found)
|
||||
await get(found, 'bds.zip')
|
||||
console.info('⚡ Unzipping')
|
||||
// Unzip server
|
||||
if (process.platform === 'linux') cp.execSync('unzip -u bds.zip && chmod +777 ./bedrock_server')
|
||||
else cp.execSync('tar -xf bds.zip')
|
||||
return verStr
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
'level-generator': '2',
|
||||
'server-port': '19130',
|
||||
'online-mode': 'false'
|
||||
}
|
||||
|
||||
// Setup the server
|
||||
function configure (options = {}) {
|
||||
const opts = { ...defaultOptions, ...options }
|
||||
let config = fs.readFileSync('./server.properties', 'utf-8')
|
||||
config += '\nplayer-idle-timeout=1\nallow-cheats=true\ndefault-player-permission-level=operator'
|
||||
for (const o in opts) config += `\n${o}=${opts[o]}`
|
||||
fs.writeFileSync('./server.properties', config)
|
||||
}
|
||||
|
||||
function run (inheritStdout = true) {
|
||||
const exe = process.platform === 'win32' ? 'bedrock_server.exe' : './bedrock_server'
|
||||
return cp.spawn(exe, inheritStdout ? { stdio: 'inherit' } : {})
|
||||
}
|
||||
|
||||
let lastHandle
|
||||
|
||||
// Run the server
|
||||
async function startServer (version, onStart, options = {}) {
|
||||
const os = process.platform === 'win32' ? 'win' : process.platform
|
||||
if (os !== 'win' && os !== 'linux') {
|
||||
throw Error('unsupported os ' + os)
|
||||
}
|
||||
await download(os, version, options.path)
|
||||
configure(options)
|
||||
const handle = lastHandle = run(!onStart)
|
||||
handle.on('error', (...a) => {
|
||||
console.warn('*** THE MINECRAFT PROCESS CRASHED ***', a)
|
||||
handle.kill('SIGKILL')
|
||||
})
|
||||
if (onStart) {
|
||||
let stdout = ''
|
||||
handle.stdout.on('data', data => {
|
||||
stdout += data
|
||||
if (stdout.includes('Server started')) onStart()
|
||||
})
|
||||
handle.stdout.pipe(process.stdout)
|
||||
handle.stderr.pipe(process.stdout)
|
||||
}
|
||||
return handle
|
||||
}
|
||||
|
||||
// Start the server and wait for it to be ready, with a timeout
|
||||
async function startServerAndWait (version, withTimeout, options) {
|
||||
let handle
|
||||
await waitFor(async res => {
|
||||
handle = await startServer(version, res, options)
|
||||
}, withTimeout, () => {
|
||||
handle?.kill()
|
||||
throw new Error(`Server did not start on time (${withTimeout}ms, now ${Date.now()})`)
|
||||
})
|
||||
return handle
|
||||
}
|
||||
|
||||
async function startServerAndWait2 (version, withTimeout, options) {
|
||||
try {
|
||||
return await startServerAndWait(version, 1000 * 60, options)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
console.log('^ Tring once more to start server in 10 seconds...')
|
||||
lastHandle?.kill()
|
||||
await new Promise(resolve => setTimeout(resolve, 10000))
|
||||
process.chdir(__dirname)
|
||||
fs.rmSync('bds-' + version, { recursive: true })
|
||||
return await startServerAndWait(version, withTimeout, options)
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
if (!module.parent) {
|
||||
// if (process.argv.length < 3) throw Error('Missing version argument')
|
||||
startServer(process.argv[2] || '1.17.10', null, process.argv[3] ? { 'server-port': process.argv[3], 'online-mode': !!process.argv[4] } : undefined)
|
||||
}
|
||||
|
||||
module.exports = { fetchLatestStable, startServer, startServerAndWait, startServerAndWait2 }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue