├── win_start.bat ├── linux_start.sh ├── .vscode └── settings.json ├── README.md ├── public └── logo.png ├── src ├── berp │ ├── auth │ │ ├── index.ts │ │ ├── CachePlugin.ts │ │ └── AuthHandler.ts │ ├── http │ │ ├── index.ts │ │ ├── Request.ts │ │ └── SequentialBucket.ts │ ├── network │ │ ├── index.ts │ │ ├── NetworkManager.ts │ │ ├── ConnectionManager.ts │ │ └── ConnectionHandler.ts │ ├── raknet │ │ ├── index.ts │ │ ├── Serializer.ts │ │ ├── UDP.ts │ │ ├── Encryption.ts │ │ ├── PacketHandler.ts │ │ └── Manager.ts │ ├── utils │ │ ├── authTools.ts │ │ ├── index.ts │ │ ├── UUID.ts │ │ ├── fsUtil.ts │ │ ├── provider.ts │ │ └── compileProtocol.ts │ ├── plugin │ │ └── pluginapi │ │ │ ├── events │ │ │ ├── events │ │ │ │ ├── index.ts │ │ │ │ ├── PlayerInitialized.ts │ │ │ │ ├── PlayerLeft.ts │ │ │ │ ├── PlayerDied.ts │ │ │ │ ├── PlayerJoin.ts │ │ │ │ ├── PlayerMessage.ts │ │ │ │ └── ChangeSkin.ts │ │ │ └── EventManager.ts │ │ │ ├── socket │ │ │ ├── requests │ │ │ │ ├── index.ts │ │ │ │ ├── Message.ts │ │ │ │ ├── heartbeat.ts │ │ │ │ ├── EntityCreate.ts │ │ │ │ ├── EntityDestroyed.ts │ │ │ │ ├── NameTagChanged.ts │ │ │ │ ├── GetEntities.ts │ │ │ │ ├── GetPlayers.ts │ │ │ │ ├── PlayerMessage.ts │ │ │ │ ├── GetRequests.ts │ │ │ │ └── enable.ts │ │ │ └── SocketManager.ts │ │ │ ├── command │ │ │ ├── ConsoleCommand.ts │ │ │ └── CommandManager.ts │ │ │ ├── world │ │ │ └── WorldManager.ts │ │ │ ├── entity │ │ │ ├── EntityManager.ts │ │ │ └── Entity.ts │ │ │ ├── player │ │ │ ├── PlayerManager.ts │ │ │ └── Player.ts │ │ │ └── PluginApi.ts │ ├── index.ts │ └── command │ │ └── CommandManager.ts ├── console │ ├── index.ts │ ├── commands │ │ ├── base │ │ │ └── BaseCommand.ts │ │ ├── kill.ts │ │ ├── reload.ts │ │ ├── plugins.ts │ │ ├── quit.ts │ │ ├── index.ts │ │ ├── recompile.ts │ │ ├── help.ts │ │ ├── connections.ts │ │ ├── external.ts │ │ ├── disconnect.ts │ │ ├── account.ts │ │ └── connect.ts │ ├── logger.ts │ ├── commandHandler.ts │ └── console.ts ├── utils │ ├── stripFormat.ts │ ├── index.ts │ ├── mixins.ts │ ├── logLogo.ts │ └── consoleOverride.ts ├── index.ts ├── Constants.ts ├── types │ └── berp.d.ts └── protodef.index.d.ts ├── data └── latest │ ├── steveSkin.bin │ ├── README.md │ └── LEGAL.md ├── .eslintignore ├── berp.json ├── win_build.bat ├── .github └── dependabot.yml ├── linux_build.sh ├── .gitignore ├── nodemon.json ├── tsconfig.json ├── docs ├── logger.md ├── command.md ├── plugin.md └── player.md ├── LICENSE ├── package.json ├── .eslintrc.js └── packets.txt /win_start.bat: -------------------------------------------------------------------------------- 1 | npm run-script dev 2 | 3 | pause -------------------------------------------------------------------------------- /linux_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | npm run-script dev 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Archived. EOL, thanks to everyone who supported us <3 2 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nobu-sh/BeRP/HEAD/public/logo.png -------------------------------------------------------------------------------- /src/berp/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CachePlugin' 2 | export * from './AuthHandler' 3 | -------------------------------------------------------------------------------- /src/berp/http/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Request' 2 | export * from './SequentialBucket' 3 | -------------------------------------------------------------------------------- /data/latest/steveSkin.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nobu-sh/BeRP/HEAD/data/latest/steveSkin.bin -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | node_modules/ 3 | dist/ 4 | tsconfig.json 5 | esm/ 6 | docs/ 7 | data/ -------------------------------------------------------------------------------- /berp.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "build": "dev", 4 | "entry": "dist/index.js" 5 | } 6 | -------------------------------------------------------------------------------- /src/console/index.ts: -------------------------------------------------------------------------------- 1 | export * from './console' 2 | export * from './logger' 3 | export * from './commandHandler' 4 | -------------------------------------------------------------------------------- /src/utils/stripFormat.ts: -------------------------------------------------------------------------------- 1 | export function stripFormat(s: string): string { 2 | return s.replace(/\u001b\[.*?m/g, "") 3 | } 4 | -------------------------------------------------------------------------------- /src/berp/network/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ConnectionHandler' 2 | export * from './NetworkManager' 3 | export * from './ConnectionManager' 4 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mixins' 2 | export * from './logLogo' 3 | export * from './consoleOverride' 4 | export * from './stripFormat' 5 | -------------------------------------------------------------------------------- /win_build.bat: -------------------------------------------------------------------------------- 1 | IF EXIST ".\package-lock.json" (del .\package-lock.json) 2 | npm i 3 | npm fund 4 | npm run-script lint 5 | npm run-script build 6 | -------------------------------------------------------------------------------- /src/berp/raknet/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Encryption' 2 | export * from './Serializer' 3 | export * from './PacketHandler' 4 | export * from './UDP' 5 | export * from './Manager' 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "17:00" 8 | timezone: America/Chicago -------------------------------------------------------------------------------- /linux_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm ./package-lock.json 3 | npm i 4 | npm fund 5 | npm run-script lint 6 | mv ./src/berp/plugin/pluginapi/PluginApi.ts ./src/berp/plugin/pluginapi/pluginApi.ts 7 | npm run-script build 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { BeRP } from "./berp" 2 | import { resolve } from 'path' 3 | import { overrideProcessConsole } from './utils' 4 | 5 | overrideProcessConsole(resolve(process.cwd(), 'logs')) 6 | 7 | new BeRP() 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | *error.log 4 | dist/ 5 | .env 6 | logs/ 7 | msal-cache/ 8 | read.js 9 | write.js 10 | size.js 11 | plugins 12 | scripts 13 | # Ignore All Protcol Version Except Latest 14 | *.*.*/ 15 | .vs/* -------------------------------------------------------------------------------- /src/berp/utils/authTools.ts: -------------------------------------------------------------------------------- 1 | import { AuthHandlerXSTSResponse } from "../../types/berp" 2 | 3 | export function createXBLToken(xstsResponse: AuthHandlerXSTSResponse): string { 4 | return `XBL3.0 x=${xstsResponse.hash};${xstsResponse.token}` 5 | } 6 | -------------------------------------------------------------------------------- /src/berp/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './UUID' 2 | export * from './protodefYamlCompiler' 3 | export * from './minecraftCompiler' 4 | export * from './fsUtil' 5 | export * from './compileProtocol' 6 | export * from './provider' 7 | export * from './authTools' 8 | -------------------------------------------------------------------------------- /src/console/commands/base/BaseCommand.ts: -------------------------------------------------------------------------------- 1 | export abstract class BaseCommand { 2 | public abstract name: string 3 | public abstract description: string 4 | public abstract usage: string 5 | public abstract aliases: string[] 6 | public abstract execute(argv: string[]): void 7 | } 8 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "restartable": "rs", 3 | "ignore": [".git", "node_modules/", "dist/", "coverage/", ".vscode/", "bin/"], 4 | "watch": ["src/"], 5 | "execMap": { 6 | "ts": "node -r ts-node/register" 7 | }, 8 | "env": { 9 | "NODE_ENV": "development" 10 | }, 11 | "ext": "js,json,ts" 12 | } -------------------------------------------------------------------------------- /src/berp/utils/UUID.ts: -------------------------------------------------------------------------------- 1 | import UUID from 'uuid-1345' 2 | 3 | export function uuidFrom (string: string): string { 4 | return UUID.v3({ 5 | namespace: "6ba7b811-9dad-11d1-80b4-00c04fd430c8", 6 | name: string, 7 | }) 8 | } 9 | 10 | export function nextUUID():string { 11 | return uuidFrom(Date.now().toString()) 12 | } 13 | -------------------------------------------------------------------------------- /data/latest/README.md: -------------------------------------------------------------------------------- 1 | # Proto Data 2 | 3 | BeRP sources its minecraft protobuf data from third parties such as: [minecraft-data](https://github.com/PrismarineJS/minecraft-data), [pocketmine](https://github.com/pmmp/PocketMine-MP), [dragonfly](https://github.com/df-mc/dragonfly) 4 | 5 | BeRP does not claim authorship of any of the items in this folder. Please support the orginal authors! 6 | 7 | [LEGAL](./LEGAL.md) 8 | -------------------------------------------------------------------------------- /src/utils/mixins.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | export function applyMixinsToClass(derivedCtor: any, ...baseCtors: any[]): void { 3 | for (const ctor of baseCtors) { 4 | for (const name of Object.getOwnPropertyNames(ctor.prototype)) { 5 | if (name !== 'constructor') { 6 | derivedCtor.prototype[name] = ctor.prototype[name] 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/logLogo.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk" 2 | export function logLogo(): void { 3 | console.log(chalk.hex('#6990ff')(` 4 | ______ ______ ______ 5 | (____ \\ (_____ \\(_____ \\ 6 | ____) )_____ _____) )_____) ) 7 | | __ (| ___ | __ /| ____/ 8 | | |__) ) ____| | \\ \\| | 9 | |______/|_____)_| |_|_| 10 | 11 | `)) 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "outDir": "dist", 7 | "declaration": false, 8 | "removeComments": false, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "skipLibCheck": true, 12 | "baseUrl": "." 13 | }, 14 | "include": [ 15 | "src", 16 | ], 17 | "exclude": [ 18 | "node_modules", 19 | "**/__tests__/*" 20 | ] 21 | } -------------------------------------------------------------------------------- /src/berp/utils/fsUtil.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | 4 | export function getFiles (dir: string): string[] { 5 | let results = [] 6 | const list = fs.readdirSync(dir) 7 | list.forEach((file) => { 8 | file = path.join(dir, file) 9 | const stat = fs.statSync(file) 10 | if (stat && stat.isDirectory()) { 11 | results = results.concat(getFiles(file)) 12 | } else { 13 | results.push(file) 14 | } 15 | }) 16 | 17 | return results 18 | } 19 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/events/events/index.ts: -------------------------------------------------------------------------------- 1 | import { PlayerJoin } from './PlayerJoin' 2 | import { PlayerLeft } from './PlayerLeft' 3 | import { PlayerInitialized } from './PlayerInitialized' 4 | import { PlayerMessage } from './PlayerMessage' 5 | import { PlayerDied } from './PlayerDied' 6 | import { ChangeSkin } from './ChangeSkin' 7 | 8 | export const defaultEvents = [ 9 | PlayerJoin, 10 | PlayerLeft, 11 | PlayerInitialized, 12 | PlayerMessage, 13 | PlayerDied, 14 | ChangeSkin, 15 | ] 16 | -------------------------------------------------------------------------------- /src/console/commands/kill.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | 4 | export class Kill extends BaseCommand { 5 | private _berp: BeRP 6 | public name = "kill" 7 | public description = "Unsafe shutdown! Kills process without preforming an exit." 8 | public usage = "" 9 | public aliases = [ 10 | "k", 11 | ] 12 | constructor(berp: BeRP) { 13 | super() 14 | this._berp = berp 15 | } 16 | public execute(): void { 17 | process.kill(process.pid) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/console/commands/reload.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | 4 | export class Reload extends BaseCommand { 5 | private _berp: BeRP 6 | public name = "reload" 7 | public description = "Reload all plugins." 8 | public usage = "" 9 | public aliases = [ 10 | "r", 11 | ] 12 | constructor(berp: BeRP) { 13 | super() 14 | this._berp = berp 15 | } 16 | public execute(): void { 17 | this._berp.getLogger().info("Attemping to reload all plugins...") 18 | this._berp.getPluginManager().reload() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/logger.md: -------------------------------------------------------------------------------- 1 | # Logger 2 | 3 | ## Referencing 4 | ```ts 5 | this.api.getLogger() 6 | ``` 7 | 8 | # Methods 9 | 10 | ## info 11 | ```ts 12 | this.api.getLogger().info('info') 13 | ``` 14 | 15 | ## warn 16 | ```ts 17 | this.api.getLogger().warn('warn') 18 | ``` 19 | 20 | ## success 21 | ```ts 22 | this.api.getLogger().success('success') 23 | ``` 24 | 25 | ## error 26 | ```ts 27 | this.api.getLogger().error('error') 28 | ``` 29 | 30 | ## debug 31 | ```ts 32 | this.api.getLogger().debug('debug') 33 | ``` 34 | 35 | ## changeColor 36 | ```ts 37 | this.api.getLogger().changeColor('red') 38 | ``` 39 | 40 | ## useHex 41 | ```ts 42 | this.api.getLogger().useHex('#FFFFFF') 43 | ``` -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/index.ts: -------------------------------------------------------------------------------- 1 | import { Message } from './Message' 2 | import { EnableRequest } from './enable' 3 | import { Heartbeat } from './heartbeat' 4 | import { GetRequests } from './GetRequests' 5 | import { EntityCreate } from './EntityCreate' 6 | import { EntityDestroyed } from './EntityDestroyed' 7 | import { PlayerMessage } from "./PlayerMessage" 8 | import { GetPlayers } from './GetPlayers' 9 | import { GetEntities } from './GetEntities' 10 | import { NameTagChanged } from './NameTagChanged' 11 | 12 | export const defaultRequests = [ 13 | Message, 14 | EnableRequest, 15 | Heartbeat, 16 | GetRequests, 17 | EntityCreate, 18 | EntityDestroyed, 19 | PlayerMessage, 20 | GetPlayers, 21 | GetEntities, 22 | NameTagChanged, 23 | ] 24 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/command/ConsoleCommand.ts: -------------------------------------------------------------------------------- 1 | import { BeRP } from 'src/berp' 2 | import { ConsoleCommandOptions } from 'src/types/berp' 3 | import { BaseCommand } from '../../../../console/commands/base/BaseCommand' 4 | 5 | export class ConsoleCommand extends BaseCommand { 6 | private _berp: BeRP 7 | public name: string 8 | public description: string 9 | public usage: string 10 | public aliases: string[] 11 | public callback: CallableFunction 12 | constructor(options: ConsoleCommandOptions, callback: CallableFunction) { 13 | super() 14 | this.name = options.command 15 | this.description = options.description 16 | this.usage = options.usage, 17 | this.aliases = options.aliases 18 | this.callback = callback 19 | } 20 | public async execute(args: string[]): Promise { 21 | this.callback(args) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/Message.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../../' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | 6 | export class Message { 7 | private _socket: SocketManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public requestName = "Message" 12 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._socket = socket 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | } 18 | public onEnabled(): void { 19 | this._socket.on("Message", (packet) => { 20 | this._socket.emit(packet.event, packet) 21 | }) 22 | } 23 | public onDisabled(): void { 24 | // 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/console/commands/plugins.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | import chalk from "chalk" 4 | 5 | export class Plugins extends BaseCommand { 6 | private _berp: BeRP 7 | public name = "plugins" 8 | public description = "Get a list of all loaded plugins and their connections." 9 | public usage = "" 10 | public aliases = [ 11 | "pl", 12 | ] 13 | constructor(berp: BeRP) { 14 | super() 15 | this._berp = berp 16 | } 17 | public execute(): void { 18 | const plugins = [] 19 | 20 | for (const [, pl] of this._berp.getPluginManager().getPlugins()) { 21 | plugins.push(`${pl.config.displayName || pl.config.name} v${pl.config.version} -- ${pl.config.description}`) 22 | } 23 | 24 | console.log(chalk.blueBright(`Found ${plugins.length} loaded plugin(s)!`)) 25 | console.log(chalk.gray(plugins.join("\n"))) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/console/commands/quit.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | 4 | export class Quit extends BaseCommand { 5 | private _berp: BeRP 6 | public name = "quit" 7 | public description = "Quit CLI and safely disable all plugin instances." 8 | public usage = "" 9 | public aliases = [ 10 | "q", 11 | "exit", 12 | "stop", 13 | ] 14 | constructor(berp: BeRP) { 15 | super() 16 | this._berp = berp 17 | } 18 | public async execute(): Promise { 19 | await this._berp.getPluginManager().killAllPlugins() 20 | this._berp.getConsole().stop() 21 | this._berp.getSequentialBucket().pauseFlush() 22 | this._berp.getSequentialBucket().emptyBucket() 23 | this._berp.getSequentialBucket().emptyFailedBucket() 24 | this._berp.getNetworkManager().kill() 25 | this._berp.getPluginManager().killTempPlugins() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/events/events/PlayerInitialized.ts: -------------------------------------------------------------------------------- 1 | import { EventManager } from "../EventManager" 2 | import { BeRP } from "src/berp" 3 | import { ConnectionHandler } from "src/berp/network" 4 | import { PluginApi } from "../../pluginApi" 5 | 6 | export class PlayerInitialized { 7 | private _events: EventManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public eventName = "PlayerInitialized" 12 | constructor(events: EventManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._events = events 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | this._connection.on('text', (packet) => { 18 | if (packet.message !== '§e%multiplayer.player.joined.realms') return 19 | 20 | return this._events.emit('PlayerInitialized', this._pluginApi.getPlayerManager().getPlayerByName(packet.paramaters[0])) 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/world/WorldManager.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionHandler } from 'src/berp/network' 2 | import { BeRP } from 'src/berp' 3 | import { PluginApi } from '../pluginApi' 4 | 5 | export class WorldManager { 6 | private _berp: BeRP 7 | private _connection: ConnectionHandler 8 | private _pluginApi: PluginApi 9 | constructor(berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 10 | this._berp = berp 11 | this._connection = connection 12 | this._pluginApi = pluginApi 13 | } 14 | public async onEnabled(): Promise { 15 | return 16 | } 17 | public async onDisabled(): Promise { 18 | return 19 | } 20 | public sendMessage(message: string): void { 21 | this._pluginApi.getCommandManager().executeCommand(`tellraw @a {"rawtext":[{"text":"${message}"}]}`) 22 | } 23 | public kickAll(reason: string): void { 24 | for (const [, player] of this._pluginApi.getPlayerManager().getPlayerList()) { 25 | player.kick(reason) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/console/commands/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { BeRP } from "../../berp" 3 | import { Quit } from "./quit" 4 | import { Help } from './help' 5 | import { Kill } from './kill' 6 | import { Recompile } from './recompile' 7 | import { Account } from './account' 8 | import { Connect } from './connect' 9 | import { Connections } from './connections' 10 | import { Disconnect } from './disconnect' 11 | import { External } from './external' 12 | import { Plugins } from './plugins' 13 | import { Reload } from './reload' 14 | class ex { 15 | public name: string 16 | public description: string 17 | public usage: string 18 | public aliases: string[] 19 | constructor(berp: BeRP) { /**/ } 20 | public execute(argv: string[]): void { /* */ } 21 | } 22 | 23 | const Commands: typeof ex[] = [ 24 | Quit, 25 | Help, 26 | Kill, 27 | Recompile, 28 | Account, 29 | Connect, 30 | Connections, 31 | Disconnect, 32 | External, 33 | Plugins, 34 | Reload, 35 | ] 36 | 37 | export { Commands } 38 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/heartbeat.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../../' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | 6 | export class Heartbeat { 7 | private _socket: SocketManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | private _totalBeats: number 12 | public requestName = "Heartbeat" 13 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 14 | this._socket = socket 15 | this._berp = berp 16 | this._connection = connection 17 | this._pluginApi = pluginApi 18 | this._totalBeats = 0 19 | } 20 | public onEnabled(): void { 21 | this._socket.on('Message', (packet) => { 22 | if (packet.event != "Heartbeat") return 23 | this._totalBeats++ 24 | }) 25 | } 26 | public onDisabled(): void { 27 | // 28 | } 29 | public getTotalBeats(): number { return this._totalBeats } 30 | } 31 | -------------------------------------------------------------------------------- /src/console/commands/recompile.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CUR_VERSION, 3 | ProtoDataPath, 4 | } from '../../Constants' 5 | import { BaseCommand } from "./base/BaseCommand" 6 | import { BeRP } from "../../berp" 7 | import { AttemptProtocolCompiler } from '../../berp/utils' 8 | import fs from 'fs' 9 | import path from 'path' 10 | 11 | export class Recompile extends BaseCommand { 12 | private _berp: BeRP 13 | public name = "recompile" 14 | public description = "Attempts to recompile protocol files. Please be aware using this while connected to realms may cause some temporary errors." 15 | public usage = "" 16 | public aliases = [ 17 | "rc", 18 | ] 19 | constructor(berp: BeRP) { 20 | super() 21 | this._berp = berp 22 | } 23 | public execute(): void { 24 | this._berp.getCommandHandler() 25 | .getLogger() 26 | .warn("Attempting protodef recompile. Process could cause temporary errors in console.") 27 | fs.rmSync(path.resolve(ProtoDataPath, CUR_VERSION), { 28 | recursive: true, 29 | }) 30 | AttemptProtocolCompiler() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/events/EventManager.ts: -------------------------------------------------------------------------------- 1 | import { BeRP } from '../../../' 2 | import { EventEmitter } from 'events' 3 | import { ConnectionHandler } from 'src/berp/network' 4 | import { PluginApi } from '../pluginApi' 5 | import { defaultEvents } from './events/index' 6 | 7 | export class EventManager extends EventEmitter { 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | private _events = new Map() 12 | constructor(berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | super() 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | this._registerEvents() 18 | } 19 | public async onEnabled(): Promise { 20 | return 21 | } 22 | public async onDisabled(): Promise { 23 | return 24 | } 25 | private _registerEvents(): void { 26 | for (const event of defaultEvents) { 27 | const newEvent = new event(this, this._berp, this._connection, this._pluginApi) 28 | this._events.set(newEvent.eventName, newEvent) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-∞ NobUwU 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/events/events/PlayerLeft.ts: -------------------------------------------------------------------------------- 1 | import { EventManager } from "../EventManager" 2 | import { BeRP } from "src/berp" 3 | import { ConnectionHandler } from "src/berp/network" 4 | import { PluginApi } from "../../pluginApi" 5 | 6 | export class PlayerLeft { 7 | private _events: EventManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public eventName = "PlayerLeft" 12 | constructor(events: EventManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._events = events 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | this._connection.on('player_list', (packet) => { 18 | for (const record of packet.records.records) { 19 | if (packet.records.type != 'remove') continue 20 | const player = this._pluginApi.getPlayerManager().getPlayerByUUID(record.uuid) 21 | this._pluginApi.getPlayerManager() 22 | .removePlayer(player) 23 | 24 | return this._events.emit('PlayerLeft', player) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/EntityCreate.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from "../../entity/Entity" 2 | import { SocketManager } from "../SocketManager" 3 | import { BeRP } from '../../../../' 4 | import { PluginApi } from '../../pluginApi' 5 | import { ConnectionHandler } from "src/berp/network" 6 | 7 | export class EntityCreate { 8 | private _socket: SocketManager 9 | private _berp: BeRP 10 | private _connection: ConnectionHandler 11 | private _pluginApi: PluginApi 12 | public requestName = "EntityCreate" 13 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 14 | this._socket = socket 15 | this._berp = berp 16 | this._connection = connection 17 | this._pluginApi = pluginApi 18 | } 19 | public onEnabled(): void { 20 | this._socket.on("EntityCreate", (packet) => { 21 | new Entity({ 22 | id: packet.entity.id, 23 | nameTag: packet.entity.nameTag, 24 | runtimeId: packet.entity.runtimeId, 25 | }, this._berp, this._connection, this._pluginApi) 26 | }) 27 | } 28 | public onDisabled(): void { 29 | // 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/berp/utils/provider.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { 3 | CUR_VERSION, 4 | ProtoDataPath, 5 | } from '../../Constants' 6 | import { resolve } from 'path' 7 | import { DataProviderKnownFiles } from '../../types/berp' 8 | import { getFiles } from '.' 9 | import fs from 'fs' 10 | 11 | export class DataProvider { 12 | public static getDataMap(): { getFile(file: DataProviderKnownFiles): Buffer | undefined } { 13 | const dataMap = new Map() 14 | const path = resolve(ProtoDataPath, CUR_VERSION) 15 | try { 16 | const files = getFiles(path) 17 | for (const file of files) { 18 | const splitFilePath = file.split(/(\/|\\)/) 19 | const fileName = splitFilePath[splitFilePath.length - 1] 20 | dataMap.set(fileName, file) 21 | } 22 | } catch {} 23 | 24 | return { 25 | getFile(file: DataProviderKnownFiles): Buffer | undefined { 26 | const path = dataMap.get(file) 27 | if (path) { 28 | return fs.readFileSync(path) 29 | } else { 30 | return undefined 31 | } 32 | }, 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/EntityDestroyed.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../../' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | 6 | export class EntityDestroyed { 7 | private _socket: SocketManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public requestName = "EntityDestroyed" 12 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._socket = socket 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | } 18 | public onEnabled(): void { 19 | this._socket.on("EntityDestroyed", (packet) => { 20 | if (!this._pluginApi.getEntityManager().getEntities() 21 | .has(packet.entity.runtimeId)) return 22 | const entity = this._pluginApi.getEntityManager().getEntityByRuntimeID(packet.entity.runtimeId) 23 | this._pluginApi.getEntityManager().removeEntity(entity) 24 | }) 25 | } 26 | public onDisabled(): void { 27 | // 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/NameTagChanged.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../../' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | 6 | export class NameTagChanged { 7 | private _socket: SocketManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public requestName = "NameTagChanged" 12 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._socket = socket 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | } 18 | public onEnabled(): void { 19 | this._socket.on("NameTagChanged", (packet) => { 20 | if (!this._pluginApi.getPlayerManager().getPlayerList() 21 | .has(packet.player) || packet.player === this._connection.getXboxProfile().extraData.displayName) return 22 | const player = this._pluginApi.getPlayerManager().getPlayerByName(packet.player) 23 | player.setNameTagBackDoor(packet.data.new) 24 | }) 25 | } 26 | public onDisabled(): void { 27 | // 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/events/events/PlayerDied.ts: -------------------------------------------------------------------------------- 1 | import { EventManager } from "../EventManager" 2 | import { BeRP } from "src/berp" 3 | import { ConnectionHandler } from "src/berp/network" 4 | import { PluginApi } from "../../pluginApi" 5 | 6 | export class PlayerDied { 7 | private _events: EventManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public eventName = "PlayerDied" 12 | constructor(events: EventManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._events = events 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | this._connection.on('text', (packet) => { 18 | if (!packet.message.startsWith('death') || packet.paramaters[0] == this._connection.getXboxProfile().extraData.displayName) return 19 | 20 | return this._events.emit('PlayerDied', { 21 | player: this._pluginApi.getPlayerManager().getPlayerByName(packet.paramaters[0]), 22 | killer: this._pluginApi.getPlayerManager().getPlayerByName(packet.paramaters[1]) || packet.paramaters[1], 23 | cause: packet.message.replace('death.', '').replace(/.generic/g, ''), 24 | }) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/entity/EntityManager.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionHandler } from 'src/berp/network' 2 | import { BeRP } from 'src/berp' 3 | import { PluginApi } from '../pluginApi' 4 | import { Entity } from './Entity' 5 | 6 | export class EntityManager { 7 | private _berp: BeRP 8 | private _connection: ConnectionHandler 9 | private _pluginApi: PluginApi 10 | private _entities = new Map() 11 | constructor(berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 12 | this._berp = berp 13 | this._connection = connection 14 | this._pluginApi = pluginApi 15 | } 16 | public async onEnabled(): Promise { 17 | return 18 | } 19 | public async onDisabled(): Promise { 20 | return 21 | } 22 | public addEntity(entity: Entity): void { 23 | this._entities.set(entity.getRuntimeID(), entity) 24 | this._pluginApi.getEventManager().emit("EntityCreate", entity) 25 | } 26 | public removeEntity(entity: Entity): void { 27 | this._entities.delete(entity.getRuntimeID()) 28 | this._pluginApi.getEventManager().emit("EntityDestroyed", entity) 29 | } 30 | public getEntityByRuntimeID(runtimID: number): Entity { return this._entities.get(runtimID) } 31 | public getEntities(): Map { return this._entities } 32 | } 33 | -------------------------------------------------------------------------------- /src/berp/network/NetworkManager.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "../../console" 2 | import { BeRP } from ".." 3 | import { ConnectionManager } from "./ConnectionManager" 4 | import { AccountInfo } from "@azure/msal-node" 5 | 6 | export class NetworkManager { 7 | private _berp: BeRP 8 | private _accounts = new Map() 9 | private _logger = new Logger("Network Manager") 10 | constructor(berp: BeRP) { 11 | this._berp = berp 12 | 13 | this._logger.success("Initialized") 14 | } 15 | public getAccounts(): Map { return this._accounts } 16 | public getLogger(): Logger { return this._logger } 17 | 18 | public create(accountInfo: AccountInfo): ConnectionManager { 19 | if (!this._accounts.get(accountInfo.username)) { 20 | const con = new ConnectionManager(accountInfo, this._berp) 21 | this._accounts.set(accountInfo.username,con) 22 | 23 | return con 24 | } 25 | } 26 | public delete(username: string): void { 27 | const account = this._accounts.get(username) 28 | if (account) { 29 | account.kill() 30 | this._accounts.delete(username) 31 | } 32 | } 33 | 34 | public kill(): void { 35 | for (const [u, cm] of this._accounts) { 36 | cm.kill() 37 | this._accounts.delete(u) 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/GetEntities.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../../' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | import { Entity } from "../../entity/Entity" 6 | 7 | export class GetEntities { 8 | private _socket: SocketManager 9 | private _berp: BeRP 10 | private _connection: ConnectionHandler 11 | private _pluginApi: PluginApi 12 | public requestName = "GetEntities" 13 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 14 | this._socket = socket 15 | this._berp = berp 16 | this._connection = connection 17 | this._pluginApi = pluginApi 18 | } 19 | public onEnabled(): void { 20 | this._socket.on("Enabled", () => { 21 | this._socket.sendPacket("GetEntities", undefined, (res) => { 22 | for (const entity of res.data) { 23 | if (this._pluginApi.getEntityManager().getEntities() 24 | .has(entity.runtimeId)) continue 25 | new Entity({ 26 | id: entity.id, 27 | nameTag: entity.nameTag, 28 | runtimeId: entity.runtimeId, 29 | }, this._berp, this._connection, this._pluginApi) 30 | } 31 | }) 32 | }) 33 | } 34 | public onDisabled(): void { 35 | // 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/events/events/PlayerJoin.ts: -------------------------------------------------------------------------------- 1 | import { BeRP } from "src/berp" 2 | import { ConnectionHandler } from "src/berp/network" 3 | import { Player } from "../../player/Player" 4 | import { EventManager } from "../EventManager" 5 | import { PluginApi } from "../../pluginApi" 6 | 7 | export class PlayerJoin { 8 | private _events: EventManager 9 | private _berp: BeRP 10 | private _connection: ConnectionHandler 11 | private _pluginApi: PluginApi 12 | public eventName = "PlayerJoin" 13 | constructor(events: EventManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 14 | this._events = events 15 | this._berp = berp 16 | this._connection = connection 17 | this._pluginApi = pluginApi 18 | this._connection.on('player_list', (packet) => { 19 | for (const player of packet.records.records) { 20 | if (this._pluginApi.getPlayerManager().getPlayerList() 21 | .has(player.username) || packet.records.type != 'add') continue 22 | 23 | return this._events.emit('PlayerJoin', new Player({ 24 | name: player.username, 25 | uuid: player.uuid, 26 | xuid: player.xbox_user_id, 27 | entityID: player.entity_unique_id, 28 | device: player.build_platform, 29 | skinData: player.skin_data, 30 | }, this._berp, this._connection, this._pluginApi)) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/events/events/PlayerMessage.ts: -------------------------------------------------------------------------------- 1 | import { EventManager } from "../EventManager" 2 | import { BeRP } from "src/berp" 3 | import { ConnectionHandler } from "src/berp/network" 4 | import { PluginApi } from "../../pluginApi" 5 | 6 | export class PlayerMessage { 7 | private _events: EventManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public eventName = "PlayerMessage" 12 | constructor(events: EventManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._events = events 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | this._connection.on('text', (packet) => { 18 | if (packet.type !== 'chat' || packet.source_name == this._connection.getXboxProfile().extraData.displayName) return 19 | const sender = this._pluginApi.getPlayerManager().getPlayerByName(packet.source_name) 20 | 21 | if (!packet.message.startsWith(this._pluginApi.getCommandManager().getPrefix())) return this._events.emit('PlayerMessage', { 22 | sender: sender, 23 | message: packet.message, 24 | }) 25 | 26 | if (sender.getRealmID() != this._connection.realm.id) return 27 | 28 | return this._events.emit('ChatCommand', { 29 | sender: sender, 30 | command: packet.message, 31 | }) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/GetPlayers.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../..' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | 6 | export class GetPlayers { 7 | private _socket: SocketManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public requestName = "GetPlayers" 12 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._socket = socket 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | } 18 | public onEnabled(): void { 19 | this._socket.on("Enabled", () => { 20 | this._socket.sendPacket("GetPlayers", undefined, (res) => { 21 | for (const entry of res.data) { 22 | if (!this._pluginApi.getPlayerManager().getPlayerList() 23 | .has(entry.name)) continue 24 | if (entry.name == this._connection.getXboxProfile().extraData.displayName) continue 25 | const player = this._pluginApi.getPlayerManager().getPlayerByName(entry.name) 26 | if (player.getNameTag() == entry.nameTag) continue 27 | player.setNameTagBackDoor(entry.nameTag) 28 | } 29 | }) 30 | }) 31 | } 32 | public onDisabled(): void { 33 | // 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/PlayerMessage.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../../' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | 6 | export class PlayerMessage { 7 | private _socket: SocketManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public requestName = "PlayerMessage" 12 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._socket = socket 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | } 18 | public onEnabled(): void { 19 | this._socket.on("PlayerMessage", (packet) => { 20 | if (!packet.message.startsWith(this._pluginApi.getCommandManager().getPrefix())) return this._pluginApi.getEventManager().emit("PlayerMessage", { 21 | message: packet.message, 22 | sender: this._pluginApi.getPlayerManager().getPlayerByName(packet.player.name || packet.player.nameTag), 23 | }) 24 | 25 | return this._pluginApi.getEventManager().emit("ChatCommand", { 26 | command: packet.message, 27 | sender: this._pluginApi.getPlayerManager().getPlayerByName(packet.player.name || packet.player.nameTag), 28 | }) 29 | }) 30 | } 31 | public onDisabled(): void { 32 | // 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/berp/auth/CachePlugin.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { TokenCacheContext } from '@azure/msal-node' 3 | 4 | interface CachePlugin { 5 | beforeCacheAccess(cacheContext: TokenCacheContext): Promise 6 | afterCacheAccess(cacheContext: TokenCacheContext): Promise 7 | } 8 | 9 | export function CachePlugin(location: string): CachePlugin { 10 | const beforeCacheAccess = (cacheContext: TokenCacheContext): Promise => { 11 | return new Promise((resolve, reject) => { 12 | if (fs.existsSync(location)) { 13 | fs.readFile(location, "utf-8", (err, data) => { 14 | if (err) { 15 | reject() 16 | } else { 17 | cacheContext.tokenCache.deserialize(data) 18 | resolve() 19 | } 20 | }) 21 | } else { 22 | fs.writeFile(location, cacheContext.tokenCache.serialize(), (err) => { 23 | if (err) { 24 | reject() 25 | } 26 | resolve() // Is needed lol 27 | }) 28 | } 29 | }) 30 | } 31 | 32 | const afterCacheAccess = (cacheContext: TokenCacheContext): Promise => { 33 | return new Promise((resolve, reject) => { 34 | if (cacheContext.cacheHasChanged) { 35 | fs.writeFile(location, cacheContext.tokenCache.serialize(), (err) => { 36 | if (err) { 37 | reject(err) 38 | } 39 | resolve() 40 | }) 41 | } else { 42 | resolve() 43 | } 44 | }) 45 | } 46 | 47 | return { 48 | beforeCacheAccess, 49 | afterCacheAccess, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/GetRequests.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../../' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | 6 | export class GetRequests { 7 | private _socket: SocketManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public requestName = "GetRequests" 12 | public requests = new Map() 13 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 14 | this._socket = socket 15 | this._berp = berp 16 | this._connection = connection 17 | this._pluginApi = pluginApi 18 | } 19 | public onEnabled(): void { 20 | this._socket.on("Enabled", () => { 21 | this._socket.sendMessage({ 22 | berp: { 23 | event: "GetRequests", 24 | requestId: this._socket.newUUID(), 25 | }, 26 | }, (res) => { 27 | for (const request of res.data) { 28 | this.requests.set(request.request, request) 29 | } 30 | }) 31 | }) 32 | 33 | this._socket.on('Message', (packet) => { 34 | if (packet.event != "EnableSocket" || this._socket.enabled == true) return 35 | this._socket.enabled = true 36 | this._socket.emit("Enabled", packet) 37 | 38 | return this._socket.sendMessage({ 39 | berp: { 40 | event: "GetRequests", 41 | requestId: packet.requestId, 42 | }, 43 | }) 44 | }) 45 | } 46 | public onDisabled(): void { 47 | // 48 | } 49 | public getRequests(): Map { return this.requests } 50 | } 51 | -------------------------------------------------------------------------------- /src/console/commands/help.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | import chalk from "chalk" 4 | 5 | export class Help extends BaseCommand { 6 | private _berp: BeRP 7 | public name = "help" 8 | public description = "Get a list of all available commands or info on a specfic command." 9 | public usage = "[command]" 10 | public aliases = [ 11 | "h", 12 | ] 13 | constructor(berp: BeRP) { 14 | super() 15 | this._berp = berp 16 | } 17 | public execute(args: string[]): void { 18 | const commands = this._berp.getCommandHandler().getCommands() 19 | 20 | if (!args[0]) { 21 | let log = `${chalk.blueBright("Active BeRP Session - Command List:")}\n` 22 | for (const command of commands.values()) { 23 | if (command.showInList == false) continue 24 | log += `${chalk.gray(" -")} ${chalk.grey(`${command.options.name}`)}\n` 25 | } 26 | console.log(log) 27 | } else { 28 | const commandName = args[0].toLowerCase() 29 | const command = [...commands.values()].find(c => c.options.name === commandName || c.options.name.includes(commandName)) 30 | 31 | if (!command) return this._berp.getCommandHandler().error(`Unknown commmand "${commandName}"!`) 32 | 33 | console.log(`${chalk.blueBright(`Active BeRP Session - Command - ${commandName}:`)}\n${chalk.gray(" name:")} ${chalk.gray(command.options.name)}\n${chalk.gray(" usage:")} ${command.options.usage ? `${chalk.gray(commandName)} ${chalk.gray(command.options.usage)}` : ""}\n${chalk.gray(" description:")} ${chalk.gray(command.options.description)}\n${chalk.gray(" aliases:")} ${chalk.gray(command.options.aliases?.join(chalk.gray(", ")))}\n`) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/events/events/ChangeSkin.ts: -------------------------------------------------------------------------------- 1 | import { EventManager } from "../EventManager" 2 | import { BeRP } from "src/berp" 3 | import { ConnectionHandler } from "src/berp/network" 4 | import { PluginApi } from "../../pluginApi" 5 | import Jimp from "jimp" 6 | export class ChangeSkin { 7 | private _events: EventManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public eventName = "ChangeSkin" 12 | constructor(events: EventManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._events = events 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | this._connection.on('player_skin', async(packet) => { 18 | const skin = packet.skin.skin_data 19 | const arrByte = Uint8ClampedArray.from(skin.data) 20 | 21 | const buffer = new Uint8ClampedArray(skin.width * skin.height * 4) 22 | 23 | for(let y = 0; y < skin.height; y++) { 24 | for(let x = 0; x < skin.width; x++) { 25 | const pos = (y * skin.width + x) * 4 26 | buffer[pos] = arrByte[pos + 0] 27 | buffer[pos + 1] = arrByte[pos + 1] 28 | buffer[pos + 2] = arrByte[pos + 2] 29 | buffer[pos + 3] = arrByte[pos + 3] 30 | } 31 | } 32 | 33 | new Jimp({ 34 | data: buffer, 35 | width: skin.width, 36 | height: skin.height, 37 | },async(err, image) =>{ 38 | return await this._events.emit('ChangeSkin', { 39 | raw:packet.skin, 40 | base64:await image.getBase64Async(Jimp.MIME_PNG), 41 | player:this._pluginApi.getPlayerManager().getPlayerByUUID(packet.uuid), 42 | }) 43 | }) 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/console/commands/connections.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | import chalk from 'chalk' 4 | export class Connections extends BaseCommand { 5 | private _berp: BeRP 6 | public name = "connections" 7 | public description = "Get a list of all current realm connection." 8 | public usage = "" 9 | public aliases = [ 10 | "cs", 11 | ] 12 | constructor(berp: BeRP) { 13 | super() 14 | this._berp = berp 15 | } 16 | public execute(): void { 17 | const accounts = this._berp.getNetworkManager().getAccounts() 18 | let log = `${chalk.blueBright("Active BeRP Session - Connections:")}\n` 19 | if (!accounts.size) return console.log(log += chalk.red(" No active connections. Use \"connect\" to connect a realm!")) 20 | 21 | const lastAct = Array.from(accounts.keys())[accounts.size - 1] 22 | log += chalk.gray(` │\n`) 23 | for (const [username, conn] of accounts) { 24 | if (username === lastAct) { 25 | log += chalk.gray(` └──${username}\n`) 26 | } else { 27 | log += chalk.gray(` ├──${username}\n`) 28 | } 29 | if (conn.getConnections().size) { 30 | const lastCon = Array.from(conn.getConnections().keys())[conn.getConnections().size - 1] 31 | for (const [id, con] of conn.getConnections()) { 32 | if (username !== lastAct) { 33 | log += chalk.gray(` │`) 34 | } else { 35 | log += chalk.gray(` `) 36 | } 37 | if (lastCon === id) { 38 | log += chalk.gray(` └──${con.realm.name.replace(/§\S/g, "")} (${id})\n`) 39 | } else { 40 | log += chalk.gray(` ├──${con.realm.name.replace(/§\S/g, "")} (${id})\n`) 41 | } 42 | } 43 | } 44 | if (username !== lastAct) log += chalk.gray(` │\n`) 45 | } 46 | 47 | console.log(log) 48 | } 49 | } 50 | // ─│├ └ 51 | -------------------------------------------------------------------------------- /docs/command.md: -------------------------------------------------------------------------------- 1 | # CommandManager 2 | 3 | ## Referencing 4 | ```ts 5 | this.api.getCommandManager() 6 | ``` 7 | 8 | # Methods 9 | 10 | ## executeCommand 11 | ```ts 12 | this.api.getCommandManager().executeCommand('say Hello World!') 13 | 14 | this.api.getCommandManager().executeCommand('say Hello World!', (res) => { 15 | console.log(res) 16 | }) 17 | ``` 18 | res is the result of the executed command. It returns as a command_output packet. Also when having a callback, the Gamerule sendcommandfeedback will be toggled from true to false. 19 | 20 | Parameters: 21 | ``` 22 | command: string 23 | callback?: (data: PacketCommandOutput) => void 24 | ``` 25 | Types: 26 | *[PacketCommandOutput](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* 27 | 28 | ## registerConsoleCommand 29 | ```ts 30 | this.api.getCommandManager().registerConsoleCommand({ 31 | command: "ping", 32 | aliases: ["p"], 33 | description: "Ping BeRP Client.", 34 | usage: "ping", 35 | }, (args) => { 36 | console.log(args) 37 | }) 38 | ``` 39 | Parameters: 40 | ``` 41 | options: ConsoleCommandOptions 42 | callback: (data: string[]) => void 43 | ``` 44 | Types: 45 | *[ConsoleCommandOptions](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* 46 | 47 | ## registerCommand 48 | ```ts 49 | this.api.getCommandManager().registerCommand({ 50 | command: "ping", 51 | description: 'Ping the server.', 52 | aliases: ['p'], 53 | }, (res) => { 54 | console.log(res) 55 | }) 56 | ``` 57 | Parameters: 58 | ``` 59 | options: CommandOptions 60 | callback: (data: CommandResponse) => void 61 | ``` 62 | Types: 63 | *[CommandOptions](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)*, 64 | *[CommandResponse](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* 65 | 66 | ## setPrefix 67 | ```ts 68 | this.api.getCommandManager().setPrefix('-') 69 | ``` 70 | 71 | ## getPrefix 72 | ```ts 73 | const prefix: string = this.api.getCommandManager().getPrefix() 74 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "berp", 3 | "version": "1.0.0", 4 | "description": "a raknet implementation solution for bedrock edition realms", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "start": "cross-env NODE_ENV=production node dist/index.js", 9 | "dev": "cross-env NODE_ENV=development ts-node src/index.ts", 10 | "lint": "eslint src/**" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/NobUwU/BeRP.git" 15 | }, 16 | "keywords": [ 17 | "mcbe", 18 | "raknet", 19 | "realms", 20 | "berp", 21 | "bewss", 22 | "utilities", 23 | "minecraft", 24 | "bedrock" 25 | ], 26 | "author": "NobUwU", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/NobUwU/BeRP/issues" 30 | }, 31 | "homepage": "https://github.com/NobUwU/BeRP#readme", 32 | "devDependencies": { 33 | "@types/js-yaml": "^4.0.2", 34 | "@types/jsonwebtoken": "^8.5.4", 35 | "@types/node": "^16.3.2", 36 | "@types/readable-stream": "^2.3.11", 37 | "@types/uuid-1345": "^0.99.22", 38 | "@typescript-eslint/eslint-plugin": "^4.28.3", 39 | "@typescript-eslint/parser": "^4.28.3", 40 | "babel-eslint": "^10.1.0", 41 | "cross-env": "^7.0.3", 42 | "eslint": "^7.30.0", 43 | "nodemon": "^2.0.12", 44 | "ts-node": "^10.3.0", 45 | "typescript": "^4.3.5" 46 | }, 47 | "dependencies": { 48 | "@azure/msal-node": "^1.2.0", 49 | "jimp": "^0.16.1", 50 | "@types/uuid": "^8.3.1", 51 | "@xboxreplay/xboxlive-auth": "^3.3.3", 52 | "axios": "^0.21.1", 53 | "chalk": "^4.1.1", 54 | "cli-select": "^1.1.2", 55 | "enquirer": "github:NobUwU/enquirer#typings", 56 | "js-yaml": "^4.1.0", 57 | "jsonwebtoken": "^8.5.1", 58 | "moment": "^2.29.1", 59 | "prismarine-nbt": "^1.5.0", 60 | "protodef": "^1.14.0", 61 | "raknet-native": "^1.0.6", 62 | "uuid": "^8.3.2", 63 | "uuid-1345": "^1.0.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/utils/consoleOverride.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-rest-params */ 2 | import fs from 'fs' 3 | import { resolve } from 'path' 4 | import { format } from 'util' 5 | 6 | /** 7 | * Overrides basic console methods 8 | * @param dir Directory To Store Log History In 9 | */ 10 | export const overrideProcessConsole = (dir: string): void => { 11 | const lastSessionFile = resolve(dir, 'last-session.log') 12 | const combinedFile = resolve(dir, 'combined.log') 13 | 14 | fs.mkdirSync(dir, { recursive: true }) 15 | const lastSessionStream = fs.createWriteStream(lastSessionFile) 16 | const combinedStream = fs.createWriteStream(combinedFile, { flags: "a" }) 17 | 18 | function write(item: string): void { 19 | lastSessionStream.write(item) 20 | combinedStream.write(item) 21 | } 22 | function closeStreams(): void { 23 | lastSessionStream.close() 24 | combinedStream.close() 25 | } 26 | 27 | process.once('beforeExit', () => { 28 | closeStreams() 29 | }) 30 | console.log = function(): void { 31 | process.stdout.write(format.apply(this, arguments) + '\n') 32 | write((format.apply(this, arguments)) 33 | .replace(/\u001b\[.*?m/g, "") + "\n") 34 | } 35 | console.info = function(): void { 36 | process.stdout.write(format.apply(this, arguments) + '\n') 37 | write((format.apply(this, arguments)) 38 | .replace(/\u001b\[.*?m/g, "") + "\n") 39 | } 40 | console.warn = function(): void { 41 | process.stdout.write(format.apply(this, arguments) + '\n') 42 | write((format.apply(this, arguments)) 43 | .replace(/\u001b\[.*?m/g, "") + "\n") 44 | } 45 | console.debug = function(): void { 46 | process.stdout.write(format.apply(this, arguments) + '\n') 47 | write((format.apply(this, arguments)) 48 | .replace(/\u001b\[.*?m/g, "") + "\n") 49 | } 50 | console.error = function(): void { 51 | process.stderr.write(format.apply(this, arguments) + '\n') 52 | write((format.apply(this, arguments)) 53 | .replace(/\u001b\[.*?m/g, "") + "\n") 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/console/logger.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import chalk from 'chalk' 3 | import { LoggerColors } from '../types/berp' 4 | export class Logger { 5 | private _manager: string 6 | private _color: LoggerColors | string 7 | private _chalkColor: chalk.Chalk 8 | constructor(manager: string, color?: string) { 9 | this._manager = manager 10 | this._color = color || 'cyan' 11 | this._assignChalkColor() 12 | } 13 | private _assignChalkColor(): void { 14 | if (this._color.startsWith("#")) { 15 | this._chalkColor = chalk.hex(this._color) 16 | } else { 17 | this._chalkColor = chalk[this._color] 18 | } 19 | } 20 | public changeColor(newColor: LoggerColors): void { 21 | this._color = newColor 22 | this._assignChalkColor() 23 | } 24 | /** 25 | * Use hex color for console instead 26 | * 27 | * `EG: #ff69b4` 28 | */ 29 | public useHex(newColor: string): void { 30 | this._color = newColor 31 | this._assignChalkColor() 32 | } 33 | public info(...content: unknown[]): void { 34 | console.info(`${chalk.gray(moment().format(""))} ${this._chalkColor(`[${this._manager}]`)} ${chalk.cyan("[Info]")}`, ...content) 35 | } 36 | public success(...content: unknown[]): void { 37 | console.log(`${chalk.gray(moment().format(""))} ${this._chalkColor(`[${this._manager}]`)} ${chalk.green("[Success]")}`, ...content) 38 | } 39 | public warn(...content: unknown[]): void { 40 | console.warn(`${chalk.gray(moment().format(""))} ${this._chalkColor(`[${this._manager}]`)} ${chalk.yellow("[Warn]")}`, ...content) 41 | } 42 | public error(...content: unknown[]): void { 43 | console.error(`${chalk.gray(moment().format(""))} ${this._chalkColor(`[${this._manager}]`)} ${chalk.red("[Error]")}`, ...content) 44 | } 45 | public debug(...content: unknown[]): void { 46 | console.debug(`${chalk.gray(moment().format(""))} ${this._chalkColor(`[${this._manager}]`)} ${chalk.magenta("[Debug]")}`, ...content) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/requests/enable.ts: -------------------------------------------------------------------------------- 1 | import { SocketManager } from "../SocketManager" 2 | import { BeRP } from '../../../../' 3 | import { PluginApi } from '../../pluginApi' 4 | import { ConnectionHandler } from "src/berp/network" 5 | 6 | export class EnableRequest { 7 | private _socket: SocketManager 8 | private _berp: BeRP 9 | private _connection: ConnectionHandler 10 | private _pluginApi: PluginApi 11 | public requestName = "EnableRequest" 12 | constructor(socket: SocketManager, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 13 | this._socket = socket 14 | this._berp = berp 15 | this._connection = connection 16 | this._pluginApi = pluginApi 17 | } 18 | public onEnabled(): void { 19 | this._socket.on('Message', (packet) => { 20 | if (packet.event != "EnableSocket" || this._socket.enabled == true) return 21 | this._socket.enabled = true 22 | this._socket.emit("Enabled", packet) 23 | if (!packet.data) { 24 | this._pluginApi.getLogger().success("Socket connection established!") 25 | } else { 26 | this._pluginApi.getLogger().success(`Socket connection established using ${packet.data.api || "unkown"} v${packet.data.version || "unkown"} for Minecraft: Bedrock v${packet.data.mcbe || "unkown"} (${packet.data.protocol || "unkown"})!`) 27 | this._socket._api = packet.data.api || "unkown" 28 | this._socket._verison = packet.data.version || "unkown" 29 | this._socket._mcbe = packet.data.mcbe || "unkown" 30 | this._socket._protocol = packet.data.protocol || "unkown" 31 | this._pluginApi.getCommandManager().registerCommand({ 32 | command: packet.data.api.toLocaleLowerCase(), 33 | description: "Returns information about the current socket manager.", 34 | }, (res) => { 35 | res.sender.sendMessage(`§7BeRP SocketManager is using §9${packet.data.api} v${packet.data.version}§7 for §aMinecraft: Bedrock Edition v${packet.data.mcbe} §7(§a${packet.data.protocol}§7).`) 36 | }) 37 | } 38 | 39 | return this._socket.sendMessage({ 40 | berp: { 41 | event: "EnableRequest", 42 | requestId: packet.requestId, 43 | }, 44 | }) 45 | }) 46 | } 47 | public onDisabled(): void { 48 | // 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/berp/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BerpConsole, 3 | CommandHandler, 4 | Logger, 5 | } from '../console' 6 | import { 7 | SequentialBucket, 8 | Request, 9 | } from './http' 10 | import { logLogo } from '../utils' 11 | import { AttemptProtocolCompiler } from './utils' 12 | import { NetworkManager } from './network' 13 | import { AuthHandler } from './auth' 14 | import { PluginManager } from './plugin/PluginManager' 15 | import { resolve } from 'path' 16 | import * as Constants from '../Constants' 17 | import { CommandManager } from './command/CommandManager' 18 | export class BeRP { 19 | private _console: BerpConsole 20 | private _commandHandler: CommandHandler 21 | private _networkManager: NetworkManager 22 | private _authProvider: AuthHandler 23 | private _sequentialBucket: SequentialBucket 24 | private _commandManager: CommandManager 25 | private _pluginManager: PluginManager 26 | private _logger = new Logger('BeRP', '#6990ff') 27 | constructor() { 28 | logLogo() 29 | 30 | this._logger.info("Preparing Modules...") 31 | AttemptProtocolCompiler() 32 | 33 | this._networkManager = new NetworkManager(this) 34 | this._sequentialBucket = new SequentialBucket(5, new Logger("Sequential Bucket", "#8769ff")) 35 | this._authProvider = new AuthHandler({ 36 | clientId: Constants.AzureClientID, 37 | authority: Constants.Endpoints.Authorities.MSAL, 38 | cacheDir: resolve(process.cwd(), 'msal-cache'), 39 | }) 40 | this._authProvider.createApp(this._authProvider.createConfig()) 41 | this._commandManager = new CommandManager(this) 42 | this._pluginManager = new PluginManager(this) 43 | this._console = new BerpConsole() 44 | this._commandHandler = new CommandHandler(this) 45 | } 46 | public getConsole(): BerpConsole { return this._console } 47 | public getLogger(): Logger { return this._logger } 48 | public getCommandHandler(): CommandHandler { return this._commandHandler } 49 | public getNetworkManager(): NetworkManager { return this._networkManager } 50 | public getAuthProvider(): AuthHandler { return this._authProvider } 51 | public getSequentialBucket(): SequentialBucket { return this._sequentialBucket } 52 | public getCommandManager(): CommandManager { return this._commandManager } 53 | public getPluginManager(): PluginManager { return this._pluginManager } 54 | public Request = Request 55 | } 56 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/entity/Entity.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionHandler } from 'src/berp/network' 2 | import { BeRP } from 'src/berp' 3 | import { PluginApi } from '../pluginApi' 4 | import { 5 | BlockPos, EntityOptions, 6 | } from 'src/types/berp' 7 | 8 | export class Entity { 9 | private _id: string 10 | private _nameTag: string 11 | private _runtimeId: number 12 | private _berp: BeRP 13 | private _connection: ConnectionHandler 14 | private _pluginApi: PluginApi 15 | 16 | constructor(options: EntityOptions, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 17 | this._id = options.id 18 | this._nameTag = options.nameTag 19 | this._runtimeId = options.runtimeId 20 | this._berp = berp 21 | this._connection = connection 22 | this._pluginApi = pluginApi 23 | this._pluginApi.getEntityManager().addEntity(this) 24 | } 25 | public getID(): string { return this._id } 26 | public getNameTag(): string { return this._nameTag } 27 | public getRuntimeID(): number { return this._runtimeId } 28 | public executeCommand(command: string): void { 29 | this._pluginApi.getSocketManager().sendMessage({ 30 | berp: { 31 | event: "UpdateEntity", 32 | entity: this._runtimeId, 33 | data: { 34 | command: command, 35 | }, 36 | requestId: this._pluginApi.getSocketManager().newUUID(), 37 | }, 38 | }) 39 | } 40 | public setNameTag(nameTag: string): void { 41 | this._nameTag = nameTag 42 | this._pluginApi.getSocketManager().sendMessage({ 43 | berp: { 44 | event: "UpdateEntity", 45 | entity: this._runtimeId, 46 | data: { 47 | nameTag: nameTag, 48 | }, 49 | requestId: this._pluginApi.getSocketManager().newUUID(), 50 | }, 51 | }) 52 | } 53 | public async getLocation(): Promise { 54 | return new Promise((res) => { 55 | this._pluginApi.getSocketManager().sendMessage({ 56 | berp: { 57 | event: "EntityRequest", 58 | entity: this._runtimeId, 59 | requestId: this._pluginApi.getSocketManager().newUUID(), 60 | }, 61 | }, (packet) => { 62 | if (!packet.entity) return res({ 63 | x: 0, 64 | y: 0, 65 | z: 0, 66 | }) 67 | 68 | return res(packet.entity.location) 69 | }) 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/console/commands/external.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | 4 | export class External extends BaseCommand { 5 | private _berp: BeRP 6 | public name = "external" 7 | public description = "Connect to a external server using your microsoft account." 8 | public usage = " " 9 | public aliases = [ 10 | "x", 11 | "ex", 12 | ] 13 | constructor(berp: BeRP) { 14 | super() 15 | this._berp = berp 16 | } 17 | public async execute(args: string[]): Promise { 18 | if (!args[0]) return this._berp.getCommandHandler().error(`Invalid argument at "${this.name} >>${args[0] || " "}<<".`) 19 | if (!args[1]) return this._berp.getCommandHandler().error(`Invalid argument at "${this.name} ${args[0]} >>${args[1] || " "}<<".`) 20 | const ip = args[0] 21 | const port = parseInt(args[1]) 22 | 23 | const accounts = await this._berp 24 | .getAuthProvider() 25 | .getCache() 26 | .getAllAccounts() 27 | 28 | if (!accounts.length) return this._berp.getCommandHandler().error("There are no active accounts linked to BeRP! Please use \"account add\" to link a new account!") 29 | 30 | this._berp.getConsole() 31 | .sendSelectPrompt("Select which account you would like to use", accounts.map(a => `${a.name} (${a.username})`)) 32 | .then(async (r) => { 33 | if (r) { 34 | try { 35 | const username = /\(.*\)/.exec(r)[0].replace(/(\(|\))/g, "") 36 | const account = accounts.find(a => a.username === username) 37 | if (!account) { 38 | return this._berp.getNetworkManager().getLogger() 39 | .error(`Failed to select account "${username}"`) 40 | } 41 | 42 | let net = this._berp.getNetworkManager().getAccounts() 43 | .get(account.username) 44 | if (!net) { 45 | net = this._berp.getNetworkManager().create(account) 46 | } 47 | 48 | net.newConnection(ip, port, { 49 | activeSlot: 1, 50 | id: port, 51 | clubId: port, 52 | name: ip, 53 | owner: "unkown", 54 | ownerUUID: "unkown", 55 | } as any) 56 | } catch (error) { 57 | this._berp.getNetworkManager().getLogger() 58 | .error(`Failed to select account for realm connection...\n`, error) 59 | } 60 | } else { 61 | this._berp.getCommandHandler().error("Connection process canceled!") 62 | } 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/console/commandHandler.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "../console" 2 | import { Commands } from "./commands" 3 | import { BaseCommand } from "./commands/base/BaseCommand" 4 | import { BeRP } from "../berp" 5 | import chalk from "chalk" 6 | import { ConsoleCommandOptions } from "src/types/berp" 7 | 8 | class CommandHandler { 9 | private _berp: BeRP 10 | private _commands = new Map() 11 | private _logger = new Logger("CLI Command Handler", '#ff6969') 12 | constructor(berp: BeRP) { 13 | this._berp = berp 14 | this._buildCommands() 15 | 16 | this._berp.getConsole().on('input', this._handleConsole.bind(this)) 17 | 18 | this._logger.success('Type "help" for a list of commands.') 19 | } 20 | public getLogger(): Logger { return this._logger } 21 | 22 | private _handleConsole(i: string):void { 23 | const args = i.split(/ /g).filter(i => i.length > 0) 24 | if (!args[0]) return 25 | const commandName = args.shift().toLowerCase() 26 | const command = this._commands.get(commandName) 27 | 28 | if (!command) return console.log(chalk.red(`Invalid commmand "${commandName}" use "help" to see all commands!`)) 29 | 30 | try { 31 | command.options.execute(args) 32 | } catch (error) { 33 | this._logger.error(error) 34 | } 35 | } 36 | private _buildCommands(): void { 37 | for (const command of Commands) { 38 | const newCommand = new command(this._berp) 39 | this._commands.set(newCommand.name, { 40 | options: newCommand, 41 | showInList: true, 42 | }) 43 | for (const aliases of newCommand.aliases) { 44 | this._commands.set(aliases, { 45 | options: newCommand, 46 | showInList: false, 47 | }) 48 | } 49 | } 50 | } 51 | 52 | public registerCommand(command: BaseCommand): void { 53 | this._commands.set(command.name, { 54 | options: command, 55 | showInList: true, 56 | }) 57 | for (const aliases of command.aliases) { 58 | this._commands.set(aliases, { 59 | options: command, 60 | showInList: false, 61 | }) 62 | } 63 | } 64 | 65 | public unregisterCommand(command: ConsoleCommandOptions): void { 66 | if (!this._commands.has(command.command)) return 67 | this._commands.delete(command.command) 68 | } 69 | 70 | public getCommands(): Map { return this._commands } 71 | 72 | public error(err: string): void { 73 | console.error(chalk.red(err)) 74 | } 75 | 76 | } 77 | 78 | export { CommandHandler } 79 | -------------------------------------------------------------------------------- /src/console/console.ts: -------------------------------------------------------------------------------- 1 | import { Select } from 'enquirer' 2 | import { stripFormat } from '../utils' 3 | import { EventEmitter } from 'events' 4 | import { Logger } from './' 5 | import chalk from 'chalk' 6 | 7 | interface BerpConsoleEvents { 8 | "input": [string] 9 | } 10 | interface BerpConsole extends EventEmitter { 11 | on(event: K, listener: (...args: BerpConsoleEvents[K]) => void): this 12 | on( 13 | event: Exclude, 14 | listener: (...args: any[]) => void, 15 | ): this 16 | once(event: K, listener: (...args: BerpConsoleEvents[K]) => void): this 17 | once( 18 | event: Exclude, 19 | listener: (...args: any[]) => void, 20 | ): this 21 | emit(event: K, ...args: BerpConsoleEvents[K]): boolean 22 | emit( 23 | event: Exclude, 24 | ...args: any[] 25 | ): boolean 26 | } 27 | 28 | class BerpConsole extends EventEmitter { 29 | private _logger = new Logger("Console", 'gray') 30 | private _isStopped = true 31 | private _listenerBinded: (data: Buffer) => void 32 | constructor() { 33 | super() 34 | this._listenerBinded = this._listener.bind(this) 35 | this.start() 36 | } 37 | public getLogger(): Logger { return this._logger } 38 | 39 | private _listener(data: Buffer): void { 40 | const clean = data.toString().replace(/(\n|\r)/g, "") 41 | this.emit('input', clean) 42 | } 43 | 44 | public start(): void { 45 | if (this._isStopped) { 46 | process.stdin.resume() 47 | process.stdin.on('data', this._listenerBinded) 48 | this._isStopped = false 49 | } 50 | } 51 | 52 | public stop(): void { 53 | if (!this._isStopped) { 54 | process.stdin.pause() 55 | process.stdin.removeListener('data', this._listenerBinded) 56 | this._isStopped = true 57 | } 58 | } 59 | 60 | public sendSelectPrompt(message: string, args: string[]): Promise { 61 | return new Promise((r) => { 62 | this.stop() 63 | new Select({ 64 | name: "selectprompt", 65 | message: `${message} ${chalk.gray('( Nav: ↑ ↓, Select: ↩, Exit: esc )')}`, 66 | choices: args.map(i => chalk.grey(i)), 67 | }) 68 | .run() 69 | .then(res => { 70 | r(stripFormat(res)) 71 | this.start() 72 | }) 73 | .catch(() => { 74 | r(undefined) 75 | this.start() 76 | }) 77 | }) 78 | } 79 | } 80 | 81 | export { BerpConsole } 82 | -------------------------------------------------------------------------------- /src/berp/raknet/Serializer.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { 3 | Compiler, 4 | FullPacketParser, 5 | Serializer, 6 | } from 'protodef' 7 | const { 8 | ProtoDefCompiler, 9 | CompiledProtodef, 10 | } = Compiler 11 | import { resolve } from 'path' 12 | import { McCompiler } from '../utils' 13 | import { CUR_VERSION } from '../../Constants' 14 | 15 | export class Parser extends FullPacketParser { 16 | constructor(option1: unknown, option2: unknown) { 17 | super(option1, option2) 18 | } 19 | public parsePacketBuffer(buffer: Buffer): { data: { name: string, params: unknown } } { 20 | try { 21 | return super.parsePacketBuffer(buffer) as any 22 | } catch (e) { 23 | throw e 24 | } 25 | } 26 | public verify (deserialized: any, serializer: any): void { 27 | const { name, params } = deserialized.data 28 | const oldBuffer = deserialized.fullBuffer 29 | const newBuffer = serializer.createPacketBuffer({ 30 | name, 31 | params, 32 | }) 33 | if (!newBuffer.equals(oldBuffer)) { 34 | throw `'Failed to re-encode', ${name}, ${JSON.stringify(params)}... (New: ${newBuffer.toString('hex')}) (Old: ${oldBuffer.toString('hex')})` 35 | } 36 | } 37 | } 38 | 39 | // Compiles the ProtoDef schema at runtime 40 | export function createProtocol (): unknown { 41 | const protocol = require(resolve(process.cwd(), `data/${CUR_VERSION}/protocol.json`)).types 42 | const compiler = new ProtoDefCompiler() 43 | compiler.addTypesToCompile(protocol) 44 | compiler.addTypes(eval(McCompiler)) 45 | compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) 46 | 47 | const compiledProto = compiler.compileProtoDefSync() 48 | 49 | return compiledProto 50 | } 51 | 52 | // Loads already generated read/write/sizeof code 53 | function getProtocol () { 54 | const compiler = new ProtoDefCompiler() 55 | compiler.addTypes(eval(McCompiler)) 56 | compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) 57 | 58 | global.PartialReadError = require('protodef/src/utils').PartialReadError 59 | const compile = (compiler, file) => require(file)(compiler.native) 60 | 61 | return new CompiledProtodef( 62 | compile(compiler.sizeOfCompiler, resolve(process.cwd(), `data/${CUR_VERSION}/size.js`)), 63 | compile(compiler.writeCompiler, resolve(process.cwd(), `data/${CUR_VERSION}/write.js`)), 64 | compile(compiler.readCompiler, resolve(process.cwd(), `data/${CUR_VERSION}/read.js`)), 65 | ) 66 | } 67 | 68 | export function createSerializer (): Serializer { 69 | const proto = getProtocol() 70 | 71 | return new Serializer(proto, 'mcpe_packet') 72 | } 73 | 74 | export function createDeserializer (): Parser { 75 | const proto = getProtocol() 76 | 77 | return new Parser(proto, 'mcpe_packet') 78 | } 79 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | parserOptions: { 7 | sourceType: 'module', 8 | parser: 'babel-eslint', 9 | allowImportExportEverywhere: false, 10 | ecmaVersion: 2020, 11 | }, 12 | extends: [ 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | ], 16 | rules: { 17 | 'semi': ['error', 'never'], 18 | 'indent': ['error', 2], 19 | 'array-bracket-newline': ['off', 'consistent'], 20 | 'array-element-newline': ['off', { 21 | multiline: true, 22 | minItems: 3, 23 | }], 24 | 'array-bracket-spacing': ['error', 'never'], 25 | 'block-spacing': ['error', 'always'], 26 | 'brace-style': ['error', '1tbs', { allowSingleLine: true }], 27 | 'eol-last': ['error', 'always'], 28 | 'function-call-argument-newline': ['off', 'consistent'], 29 | 'func-call-spacing': ['error', 'never'], 30 | 'newline-before-return': 'error', 31 | 'newline-per-chained-call': ['error', { ignoreChainWithDepth: 2 }], 32 | 'no-mixed-spaces-and-tabs': 'error', 33 | 'no-multi-assign': ['error'], 34 | 'no-multiple-empty-lines': ['error', { 35 | max: 2, 36 | maxBOF: 0, 37 | maxEOF: 0, 38 | }], 39 | 'no-spaced-func': 'error', 40 | 'object-curly-spacing': ['error', 'always'], 41 | 'object-curly-newline': ['error', { 42 | ObjectExpression: { 43 | minProperties: 4, 44 | multiline: true, 45 | consistent: true, 46 | }, 47 | ObjectPattern: { 48 | minProperties: 4, 49 | multiline: true, 50 | consistent: true, 51 | }, 52 | ImportDeclaration: { 53 | minProperties: 2, 54 | multiline: true, 55 | consistent: true, 56 | }, 57 | ExportDeclaration: { 58 | minProperties: 2, 59 | multiline: true, 60 | consistent: true, 61 | }, 62 | }], 63 | 'comma-dangle': ['error', { 64 | arrays: 'always-multiline', 65 | objects: 'always-multiline', 66 | imports: 'always-multiline', 67 | exports: 'always-multiline', 68 | functions: 'always-multiline', 69 | }], 70 | 'object-property-newline': ['error', { 71 | allowAllPropertiesOnSameLine: false, 72 | }], 73 | 'space-in-parens': ['error', 'never'], 74 | 'space-infix-ops': 'error', 75 | 'template-tag-spacing': ['error', 'always'], 76 | '@typescript-eslint/member-delimiter-style': ['error', { 77 | multiline: { 78 | delimiter: "none", 79 | requireLast: true, 80 | }, 81 | singleline: { 82 | delimiter: "comma", 83 | requireLast: false, 84 | }, 85 | }], 86 | '@typescript-eslint/no-explicit-any': 0, 87 | } 88 | } -------------------------------------------------------------------------------- /src/berp/http/Request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { 3 | RequestOptions, 4 | RequestParams, 5 | } from '../../types/berp' 6 | 7 | export class Request { 8 | private _req: RequestParams 9 | private _options: RequestOptions 10 | constructor(request?: RequestParams, options?: RequestOptions) { 11 | this._req = request || undefined 12 | this._options = Object.assign({ 13 | attempts: 5, 14 | attemptTimeout: 5000, 15 | requestTimeout: 25000, 16 | }, options || {}) 17 | } 18 | public getRequest(): RequestParams { return this._req } 19 | public setRequest(req: RequestParams): void { this._req = req } 20 | public getRequestOptions(): RequestOptions { return this._options } 21 | public setRequestOptions(options: RequestOptions): void { this._options = options } 22 | 23 | /** 24 | * Method is called once Sequential Bucket successfully makes request 25 | * 26 | * Client is expected to override this method with their own function 27 | * ___ 28 | * `Example` 29 | * ``` 30 | * Request.onFufilled = (data: MyInterface): void => { My Logic } 31 | * ``` 32 | */ 33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 34 | public onFufilled(data: R): void { 35 | /* User Inplemented Method */ 36 | } 37 | /** 38 | * Method is called when Sequential Bucket completely fails to make request 39 | * 40 | * Client is expected to override this method with their own function 41 | * ___ 42 | * `Example` 43 | * ``` 44 | * Request.onFailed = (err: MyInterface): void => { My Logic } 45 | * ``` 46 | */ 47 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 48 | public onFailed(err: unknown): void { 49 | /* User Inplemented Method */ 50 | } 51 | 52 | public async makeRequest(): Promise 53 | public async makeRequest(cb: (err: any, res: R) => void): Promise 54 | public async makeRequest(cb?: (err: any, res: R) => void): Promise { 55 | if (!this._req || !this._req.url || !this._req.method) throw Error("Request method & url required to make a request!") 56 | try { 57 | const { data }: { data: R } = await axios({ 58 | method: this._req.method, 59 | url: this._req.url, 60 | headers: this._req.headers || undefined, 61 | data: this._req.body || undefined, 62 | timeout: this._options.requestTimeout, 63 | timeoutErrorMessage: `Request failed to resolve after ${this._options.requestTimeout}ms. Failing request!`, 64 | }) 65 | if (cb) { 66 | return cb(undefined, data) 67 | } else { 68 | return new Promise((r) => { 69 | r(data) 70 | }) 71 | } 72 | } catch (error) { 73 | if (cb) { 74 | return cb(error, undefined) 75 | } else { 76 | return new Promise((r,j) => { 77 | j(error) 78 | }) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/player/PlayerManager.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionHandler } from 'src/berp/network' 2 | import { BeRP } from 'src/berp' 3 | import { PluginApi } from '../pluginApi' 4 | import { Player } from './Player' 5 | 6 | export class PlayerManager { 7 | private _berp: BeRP 8 | private _connection: ConnectionHandler 9 | private _pluginApi: PluginApi 10 | private _players = { 11 | name: new Map(), 12 | nameTag: new Map(), 13 | uuid: new Map(), 14 | xuid: new Map(), 15 | entityID: new Map(), 16 | } 17 | constructor(berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 18 | this._berp = berp 19 | this._connection = connection 20 | this._pluginApi = pluginApi 21 | } 22 | public async onEnabled(): Promise { 23 | for (const player of this._connection.playerQue) { 24 | new Player({ 25 | name: player.username, 26 | uuid: player.uuid, 27 | xuid: player.xbox_user_id, 28 | entityID: player.entity_unique_id, 29 | device: player.build_platform, 30 | skinData: player.skin_data, 31 | }, this._berp, this._connection, this._pluginApi) 32 | } 33 | } 34 | public async onDisabled(): Promise { 35 | return 36 | } 37 | public addPlayer(player: Player): void { 38 | this._players.name.set(player.getName(), player) 39 | this._players.nameTag.set(player.getNameTag(), player) 40 | this._players.uuid.set(player.getUUID(), player) 41 | this._players.xuid.set(player.getXuid(), player) 42 | this._players.entityID.set(player.getEntityID(), player) 43 | } 44 | public removePlayer(player: Player): void { 45 | this._players.name.delete(player.getName()) 46 | this._players.nameTag.delete(player.getNameTag()) 47 | this._players.uuid.delete(player.getUUID()) 48 | this._players.xuid.delete(player.getXuid()) 49 | this._players.entityID.delete(player.getEntityID()) 50 | } 51 | public getPlayerByName(name: string): Player { return this._players.name.get(name) } 52 | public getPlayerByNameTag(nameTag: string): Player { return this._players.nameTag.get(nameTag) } 53 | public getPlayerByUUID(uuid: string): Player { return this._players.uuid.get(uuid) } 54 | public getPlayerByXuid(xuid: string): Player { return this._players.xuid.get(xuid) } 55 | public getPlayerByEntityID(entityID: bigint): Player { return this._players.entityID.get(entityID) } 56 | public getPlayerList(): Map { return this._players.name } 57 | public updatePlayerNameTag(player: Player, nameTag: string, emit = true): void { 58 | this._players.nameTag.delete(player.getNameTag()) 59 | this._players.nameTag.set(nameTag, player) 60 | if (!emit) return 61 | this._pluginApi.getSocketManager().sendMessage({ 62 | berp: { 63 | event: 'UpdateNameTag', 64 | player: player.getName(), 65 | message: nameTag, 66 | requestId: this._pluginApi.getSocketManager().newUUID(), 67 | }, 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/console/commands/disconnect.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | 4 | export class Disconnect extends BaseCommand { 5 | private _berp: BeRP 6 | public name = "disconnect" 7 | public description = "Disconnect from a realm." 8 | public usage = "" 9 | public aliases = [ 10 | "dc", 11 | ] 12 | constructor(berp: BeRP) { 13 | super() 14 | this._berp = berp 15 | } 16 | public execute(): void { 17 | const accounts = this._berp.getNetworkManager().getAccounts() 18 | if (!accounts.size) return this._berp.getCommandHandler().error("There are no active connections!") 19 | 20 | const accountsWithActualConnections = Array.from(accounts.entries()) 21 | .filter(([,v]) => v.getConnections().size > 0) 22 | 23 | if (!accountsWithActualConnections.length) return this._berp.getCommandHandler().error("There are no active connections!") 24 | 25 | this._berp.getConsole() 26 | .sendSelectPrompt("Select an account to manage its connections", accountsWithActualConnections.map(([,v]) => `${v.getAccount().name} (${v.getAccount().username})`)) 27 | .then(r => { 28 | if (r) { 29 | try { 30 | const username = /\(.*\)/.exec(r)[0].replace(/(\(|\))/g, "") 31 | const account = accounts.get(username) 32 | if (!account) { 33 | return this._berp.getNetworkManager().getLogger() 34 | .error(`Failed to select account "${username}"`) 35 | } 36 | const connections = Array.from(account.getConnections().entries()) 37 | this._berp.getConsole() 38 | .sendSelectPrompt("Select which realm you would like to disconnect from", connections.map(([k,v]) => `${v.realm.name.replace(/§\S/g, "")} (${k})`)) 39 | .then(async (r) => { 40 | if (r) { 41 | try { 42 | const id = /\(.*\)/.exec(r)[0].replace(/(\(|\))/g, "") 43 | const realm = account.getConnections().get(parseInt(id)) 44 | if (!realm) { 45 | return this._berp.getNetworkManager().getLogger() 46 | .error(`Failed to select realm "${r}"`) 47 | } 48 | await this._berp.getPluginManager().killPlugins(realm) 49 | realm.close() 50 | } catch (error) { 51 | return this._berp.getNetworkManager().getLogger() 52 | .error(`Failed to select account for realm disconnection...\n`, error) 53 | } 54 | } else { 55 | this._berp.getCommandHandler().error("Disconnection process canceled!") 56 | } 57 | }) 58 | } catch (error) { 59 | return this._berp.getNetworkManager().getLogger() 60 | .error(`Failed to select account for realm disconnection...\n`, error) 61 | } 62 | } else { 63 | this._berp.getCommandHandler().error("Disconnection process canceled!") 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/berp/raknet/UDP.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { EventEmitter } from 'events' 3 | import { 4 | Client, 5 | PacketReliability, 6 | PacketPriority, 7 | } from 'raknet-native' 8 | 9 | interface RaknetEvents { 10 | connected: [] 11 | pong: [] 12 | closed: [] 13 | raw: [Buffer, string, string] 14 | } 15 | 16 | export interface Raknet { 17 | on(event: K, listener: (...args: RaknetEvents[K]) => void): this 18 | on( 19 | event: Exclude, 20 | listener: (...args: any[]) => void, 21 | ): this 22 | once(event: K, listener: (...args: RaknetEvents[K]) => void): this 23 | once( 24 | event: Exclude, 25 | listener: (...args: any[]) => void, 26 | ): this 27 | emit(event: K, ...args: RaknetEvents[K]): boolean 28 | emit( 29 | event: Exclude, 30 | ...args: any[] 31 | ): boolean 32 | writeRaw(packet: Buffer, priority?: PacketPriority, reliability?: PacketReliability, orderingChannel?: number): void 33 | } 34 | 35 | export class Raknet extends EventEmitter { 36 | public readonly host: string 37 | public readonly port: number 38 | public readonly protocolVersion: number 39 | private connected = false 40 | private connection: Client 41 | constructor(host: string, port: number, protocol: number) { 42 | super() 43 | this.host = host 44 | this.port = port 45 | this.protocolVersion = protocol 46 | 47 | this._onConnect = this._onConnect.bind(this) 48 | this._onPong = this._onPong.bind(this) 49 | this._onEncapsulated = this._onEncapsulated.bind(this) 50 | this._onClose = this._onClose.bind(this) 51 | 52 | 53 | this.connection = new Client(host, port, { protocolVersion: protocol }) 54 | this.connection.on('connect', this._onConnect) 55 | this.connection.on('pong', this._onPong) 56 | this.connection.on('encapsulated', this._onEncapsulated) 57 | this.connection.on('disconnect', this._onClose) 58 | } 59 | private _onConnect() { 60 | this.emit('connected') 61 | } 62 | private _onPong() { 63 | this.emit('pong') 64 | } 65 | private _onEncapsulated(arg: { buffer: Buffer, address: string, guid: string }) { 66 | this.emit('raw', Buffer.from(arg.buffer), arg.address, arg.guid) 67 | } 68 | private _onClose() { 69 | this.emit('closed') 70 | } 71 | public killConnection(): void { 72 | this.removeAllListeners() 73 | if (this.connection) { 74 | this.connection.close() 75 | } 76 | } 77 | public writeRaw(packet: Buffer, priority?: PacketPriority, reliability?: PacketReliability, orderingChannel?: number): void { 78 | this.connection.send(packet, priority || PacketPriority.IMMEDIATE_PRIORITY, reliability || PacketReliability.RELIABLE_ORDERED, orderingChannel || 0) 79 | } 80 | public connect(): void { 81 | if (!this.connected) { 82 | this.connected = true 83 | this.connection.connect() 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /docs/plugin.md: -------------------------------------------------------------------------------- 1 | # Creating your first plugin 2 | ## Basic info 3 | - BeRP's pluginManager allows for installing and using any node module with your plugin via npm. 4 | - Plugins are recommended to be written in TypeScript, since BeRP will automatically compile the TypeScript. 5 | - Creating plugins for BeRP can be very simple, but yet can get very complicated by the type of project you are creating. 6 | - When you run your plugin for the first time, BeRP will automatically create a folder called ```@interface``` and install a couple of file with that folder. 7 | - The file that it installed is the PluginApi interface that includes all typings for BeRP. This will allow autofill for functions and events, which is very helpfull for development. 8 | - Each time your plugin is ran, BeRP will automatically update the interface to the latest version, so you don't have to update it each time BeRP get a update. 9 | 10 | ## Package.json 11 | - The package.json for the plugin is the same as any node package, but with a few extra entries: displayName, color, and devMode. 12 | - DisplayName is what will be displayed in BeRP for the logger. 13 | - Color is what color the displayName will be in the logger. Color can take a string for the color, or a hex value. 14 | - DevMode is a boolean option that allows for the plugin to be recompiled each time it is loaded. 15 | ```json 16 | { 17 | "name": "exampleplugin", 18 | "displayName": "ExamplePlugin", 19 | "color": "red", 20 | "version": "1.0.0", 21 | "description": "examplePlugin for BeRP", 22 | "devMode": false, 23 | "main": "dist/index.js", 24 | "scripts": { 25 | "build": "tsc", 26 | "dev": "ts-node src/index.ts", 27 | "start": "node dist/index.js" 28 | }, 29 | "author": "You!", 30 | "license": "ISC", 31 | "dependencies": { 32 | "@azure/msal-node": "^1.2.0", 33 | "ts-node": "^10.0.0", 34 | "typescript": "^4.3.2" 35 | }, 36 | "devDependencies": { 37 | "@types/node": "^15.12.4" 38 | } 39 | } 40 | ``` 41 | 42 | ## Index.ts 43 | - This is the basic layout for your src code. 44 | ```ts 45 | import { 46 | PluginApi, 47 | } from './@interface/pluginApi.i' // The interface that BeRP auto downloads 48 | 49 | class examplePlugin { 50 | private api: PluginApi 51 | 52 | constructor(api: PluginApi) { 53 | this.api = api // References the pluginAPI for BeRP 54 | } 55 | 56 | async onLoaded(): Promise { 57 | this.api.getLogger().info('Plugin loaded!') 58 | // The onLoaded function is called each time the plugin is loaded by BeRP 59 | } 60 | async onEnabled(): Promise { 61 | this.api.getLogger().info('Plugin enabled!') 62 | // The onEnabled function is called each time the plugin is started 63 | } 64 | async onDisabled(): Promise { 65 | this.api.getLogger().success('Plugin disabled!') 66 | // The onDiabled function is called each time the plugin is shutting down 67 | } 68 | } 69 | 70 | export = examplePlugin 71 | 72 | ``` 73 | 74 | # Method Guids 75 | - [Logger](https://github.com/NobUwU/BeRP/blob/main/docs/logger.md) 76 | - [CommandManager](https://github.com/NobUwU/BeRP/blob/main/docs/command.md) -------------------------------------------------------------------------------- /src/berp/command/CommandManager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChatCommand, 3 | CommandMapOptions, 4 | CommandOptions, 5 | CommandResponse, 6 | ConsoleCommandOptions, 7 | } from 'src/types/berp' 8 | import { BeRP } from '..' 9 | 10 | export class CommandManager { 11 | private _berp: BeRP 12 | private _prefix = '-' 13 | private _commands = new Map() 14 | constructor(berp: BeRP) { 15 | this._berp = berp 16 | } 17 | private _parseCommand(content: string): { command: string, args: string[] } { 18 | const command = content.replace(this._prefix, '').split(' ')[0] 19 | const args = content.replace(`${this._prefix}${command} `, '').split(' ') 20 | if (args[0] == `${this._prefix}${command}`) args[0] = undefined 21 | 22 | return { 23 | command: command, 24 | args: args, 25 | } 26 | } 27 | public async executeCommand(data: ChatCommand): Promise { 28 | const parsedCommand = this._parseCommand(data.command) 29 | if (!this._commands.has(parsedCommand.command)) return data.sender.sendMessage("§cThis command doesn't exsist!") 30 | const commandData = this._commands.get(parsedCommand.command) 31 | if (!commandData.options.permissionTags) return commandData.execute({ 32 | sender: data.sender, 33 | args: parsedCommand.args, 34 | }) 35 | const tags: string[] = await data.sender.getTags() 36 | const found = tags.some(r => commandData.options.permissionTags.indexOf(r) >= 0) 37 | if (!found) return data.sender.sendMessage('§cYou dont have permission to use this command!') 38 | 39 | return commandData.execute({ 40 | sender: data.sender, 41 | args: parsedCommand.args, 42 | }) 43 | } 44 | public registerCommand(options: CommandOptions, callback: (data: CommandResponse) => void): void { 45 | if (this._commands.has(options.command)) return 46 | this._commands.set(options.command, { 47 | options: options, 48 | showInList: true, 49 | execute: callback, 50 | }) 51 | if (!options.aliases) return 52 | for (const aliases of options.aliases) { 53 | if (this._commands.has(aliases)) continue 54 | this._commands.set(aliases, { 55 | options: options, 56 | showInList: false, 57 | execute: callback, 58 | }) 59 | } 60 | } 61 | public unregisterCommand(options: CommandOptions | ConsoleCommandOptions, type: "game" | "console"): void { 62 | switch (type) { 63 | case "game": 64 | if (!this._commands.has(options.command)) return 65 | this._commands.delete(options.command) 66 | if (!options.aliases) return 67 | for (const aliases of options.aliases) { 68 | this._commands.delete(aliases) 69 | } 70 | break 71 | case "console": 72 | this._berp.getCommandHandler().unregisterCommand(options as ConsoleCommandOptions) 73 | break 74 | default: 75 | this._berp.getLogger().error('Unknown unregister type!') 76 | break 77 | } 78 | } 79 | public getCommands(): Map { return this._commands } 80 | public getPrefix(): string { return this._prefix } 81 | public setPrefix(prefix: string): void { this._prefix = prefix } 82 | } 83 | -------------------------------------------------------------------------------- /src/berp/network/ConnectionManager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AccountInfo, 3 | AuthenticationResult, 4 | } from "@azure/msal-node" 5 | import { BeRP } from "../" 6 | import { Logger } from '../../console' 7 | import { ConnectionHandler } from "./ConnectionHandler" 8 | import * as C from '../../Constants' 9 | import { RealmAPIWorld } from "src/types/berp" 10 | 11 | export class ConnectionManager { 12 | private _berp: BeRP 13 | private _logger: Logger 14 | private _account: AccountInfo 15 | private _accountAuthRes: AuthenticationResult 16 | private _accountAuthRefresh: NodeJS.Timer 17 | private _connections = new Map() 18 | constructor(account: AccountInfo, berp: BeRP) { 19 | this._berp = berp 20 | this._account = account 21 | this._logger = new Logger(`Connection Manager (${account.username})`, "#ff9169") 22 | this._startAuthRefresh() 23 | this._logger.success("Initialized") 24 | } 25 | public getAccount(): AccountInfo { return this._account } 26 | public getLogger(): Logger { return this._logger } 27 | public getConnections(): Map { return this._connections } 28 | 29 | private _startAuthRefresh(): void { 30 | this._accountAuthRefresh = setInterval(async () => { 31 | await this._authRefresh() 32 | }, 1000 * 60 * 60 * 12) // every 12 hours 33 | } 34 | 35 | private async _authRefresh(): Promise { 36 | try { 37 | const res = await this._berp.getAuthProvider().aquireTokenFromCache({ 38 | scopes: C.Scopes, 39 | account: this._account, 40 | }) 41 | 42 | this._accountAuthRes = res 43 | } catch (error) { 44 | this._logger.error(`Failed to refresh auth flow for "${this._account.username}". Terminating all connections and removing account from cache. Please reauth!\n`, error) 45 | this.kill() 46 | this._berp.getAuthProvider().getCache() 47 | .removeAccount(this._account) 48 | } 49 | } 50 | 51 | public kill(): void { 52 | if (this._accountAuthRefresh) { 53 | clearInterval(this._accountAuthRefresh) 54 | } 55 | this.closeAll() 56 | } 57 | 58 | public closeAll(): void { 59 | if (this._connections.size) { 60 | for (const [, connection] of this._connections) { 61 | connection.close() 62 | } 63 | } 64 | } 65 | public closeSingle(id: number): void { 66 | const connection = this._connections.get(id) 67 | if (connection) { 68 | connection.close() 69 | } 70 | } 71 | 72 | public async newConnection(host: string, port: number, realm: RealmAPIWorld): Promise { 73 | return new Promise(async (r, rj) => { 74 | try { 75 | if (!this._accountAuthRes) await this._authRefresh() 76 | const newConnection = new ConnectionHandler(host, port, realm, this, this._berp) 77 | const xsts = await this._berp.getAuthProvider() 78 | .ezXSTSForRealmRak(this._accountAuthRes) 79 | await newConnection.authMc(xsts) 80 | this._connections.set(realm.id, newConnection) 81 | 82 | newConnection.once('rak_ready', () => { 83 | r(newConnection) 84 | }) 85 | 86 | newConnection.connect() 87 | } catch (error) { 88 | rj(error) 89 | } 90 | }) 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/berp/raknet/Encryption.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CipherKey, 3 | BinaryLike, 4 | CipherGCMTypes, 5 | getCiphers, 6 | createCipheriv, 7 | CipherGCM, 8 | DecipherGCM, 9 | createDecipheriv, 10 | createHash, 11 | } from 'crypto' 12 | import zlib from 'zlib' 13 | 14 | export class Encryption { 15 | private cipher: CipherGCM 16 | private decipher: DecipherGCM 17 | private sendCounter: bigint 18 | private recieveCounter: bigint 19 | private iv: Buffer 20 | private secretKeyBytes: Buffer 21 | constructor(iv: Buffer, secretKeyBytes: Buffer) { 22 | this.iv = iv 23 | this.secretKeyBytes = secretKeyBytes 24 | 25 | this.cipher = this.createCipher(this.secretKeyBytes, iv.slice(0, 12), 'aes-256-gcm') 26 | this.decipher = this.createDecipher(this.secretKeyBytes, iv.slice(0, 12), 'aes-256-gcm') 27 | 28 | this.sendCounter = 0n 29 | this.recieveCounter = 0n 30 | } 31 | 32 | public getCipher(): CipherGCM { return this.cipher } 33 | public getDecipher(): DecipherGCM { return this.decipher } 34 | 35 | public createCipher(secret: CipherKey, iv: BinaryLike, alg: CipherGCMTypes): CipherGCM { 36 | if (getCiphers().includes(alg)) { 37 | return createCipheriv(alg, secret, iv) 38 | } 39 | } 40 | public createDecipher(secret: CipherKey, iv: BinaryLike, alg: CipherGCMTypes): DecipherGCM { 41 | if (getCiphers().includes(alg)) { 42 | return createDecipheriv(alg, secret, iv) 43 | } 44 | } 45 | public computeCheckSum(plain: BinaryLike, scounter: bigint, keyBytes: BinaryLike): Buffer { 46 | const digest = createHash('sha256') 47 | const counter = Buffer.alloc(8) 48 | counter.writeBigInt64LE(scounter, 0) 49 | digest.update(counter) 50 | digest.update(plain) 51 | digest.update(keyBytes) 52 | const hash = digest.digest() 53 | 54 | return hash.slice(0, 8) 55 | } 56 | public createEncryptor(): { create(blob: zlib.InputType): Promise } { 57 | const create = (chunk: zlib.InputType): Promise => { 58 | return new Promise((r) => { 59 | const def = zlib.deflateRawSync(chunk, { level: 7 }) 60 | const packet = Buffer.concat([def, this.computeCheckSum(def, this.sendCounter, this.secretKeyBytes)]) 61 | this.sendCounter++ 62 | 63 | this.cipher.once('data', (buf) => { r(buf) }) 64 | this.cipher.write(packet) 65 | }) 66 | } 67 | 68 | return { create } 69 | } 70 | public createDecryptor(): { read(blob: Buffer): Promise } { 71 | const read = (chunk: Buffer): Promise => { 72 | return new Promise((r, j) => { 73 | this.decipher.once('data', (buf: Buffer) => { 74 | const packet = buf.slice(0, buf.length - 8) 75 | const checksum = buf.slice(buf.length - 8, buf.length) 76 | const computedCheckSum = this.computeCheckSum(packet, this.recieveCounter, this.secretKeyBytes) 77 | this.recieveCounter++ 78 | 79 | if (Buffer.compare(checksum, computedCheckSum) !== 0) { 80 | j(`Checksum mismatch ${checksum.toString('hex')} != ${computedCheckSum.toString('hex')}`) 81 | } 82 | 83 | const inf = zlib.inflateRawSync(buf, { chunkSize: 1024 * 1024 * 2 }) 84 | r(inf) 85 | }) 86 | this.decipher.write(chunk) 87 | }) 88 | } 89 | 90 | return { read } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /docs/player.md: -------------------------------------------------------------------------------- 1 | # Player 2 | Represents a player within the world, and stored by BeRP. 3 | 4 | 5 | # Methods 6 | 7 | ## getName 8 | ```ts 9 | getName(): string 10 | ``` 11 | Returns the player's name of their Microsoft account. 12 | 13 | ## getNameTag 14 | ```ts 15 | getNameTag(): string 16 | ``` 17 | Returns the player's ingame name. 18 | 19 | ## getExecutionName 20 | ```ts 21 | getExecutionName(): string 22 | ``` 23 | Returns the player's proper name for command execution. 24 | 25 | ## getRealmID 26 | ```ts 27 | getRealmID(): number 28 | ``` 29 | Returns the player's current realm id. 30 | 31 | ## getUUID 32 | ```ts 33 | getUUID(): string 34 | ``` 35 | Returns the player's UUID. 36 | 37 | ## getXuid 38 | ```ts 39 | getXuid(): string 40 | ``` 41 | Returns the player's XUID. 42 | 43 | ## getEntityID 44 | ```ts 45 | getEntityID(): bigint 46 | ``` 47 | Returns the player's Minecraft entity id. 48 | 49 | ## getDevice 50 | ```ts 51 | getDevice(): Devices 52 | ``` 53 | Returns the player's current device they are playing on. 54 | 55 | Types: *[Devices](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* 56 | 57 | ## getSkingData 58 | ```ts 59 | getSkinData(): Skin 60 | ``` 61 | Returns the player's skin data. 62 | 63 | Types: *[Skin](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* 64 | 65 | ## getConnection 66 | ```ts 67 | getConnection(): ConnectionHandler 68 | ``` 69 | Returns the player's current connection that BeRP is handling. 70 | 71 | Types: *[ConnectionHandler](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* 72 | 73 | ## setNameTag 74 | ```ts 75 | setNameTag(nameTag: string): void 76 | ``` 77 | Sets the player's ingame name. 78 | 79 | Note: To use this method, you must be using [BeAPI](https://github.com/MCBE-Utilities/BeAPI), or some other Socket handler. 80 | 81 | ## sendMessage 82 | ```ts 83 | sendMessage(message: string): void 84 | ``` 85 | Sends a message to the player. 86 | 87 | ## sendTitle 88 | ```ts 89 | sendTitle(message: string, slot: 'actionbar' | 'title' | 'subtitle'): void 90 | ``` 91 | Sends a titleraw to the player 92 | 93 | ## executeCommand 94 | ```ts 95 | executeCommand(command: string, callback?: (data: PacketCommandOutput) => void): void 96 | ``` 97 | Executes a command as the player. 98 | 99 | Types: *[PacketCommandOutput](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* 100 | 101 | ## getTags 102 | ```ts 103 | getTags(): Promise 104 | ``` 105 | Returns the player's current tags. 106 | 107 | ## hasTag 108 | ```ts 109 | hasTag(tag: string): Promise 110 | ``` 111 | Returns the player's bool if the player has a certain 112 | 113 | ## removeTag 114 | ```ts 115 | removeTag(tag: string): void 116 | ``` 117 | Removes a tag from the player. 118 | 119 | ## addTag 120 | ```ts 121 | addTag(tag: string): void 122 | ``` 123 | Adds a tag to the player. 124 | 125 | ## getScore 126 | ```ts 127 | getScore(objective: string): Promise 128 | ``` 129 | Returns the player's score of an objective. 130 | 131 | ## updateScore 132 | ```ts 133 | updateScore(operation: 'add' | 'remove' | 'set', objective: string, value: number): void 134 | ``` 135 | Updates a player's score. 136 | 137 | ## kick 138 | ```ts 139 | kick(reason: string): void 140 | ``` 141 | Kicks the player from the world. 142 | 143 | ## getItemCount 144 | ```ts 145 | getItemCount(item: string): Promise 146 | ``` 147 | Returns the player's item count of a certain item. 148 | 149 | ## getLocation 150 | ```ts 151 | getLocation(): Promise 152 | ``` 153 | Returns the player's current location. 154 | 155 | Note: To use this method, you must be using [BeAPI](https://github.com/MCBE-Utilities/BeAPI), or some other Socket handler. 156 | 157 | types: *[BlockPos](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* 158 | 159 | ## getInventory 160 | ```ts 161 | getInventory(): Promise 162 | ``` 163 | Returns the player's inventory. 164 | 165 | Note: To use this method, you must be using [BeAPI](https://github.com/MCBE-Utilities/BeAPI), or some other Socket handler. 166 | 167 | types: *[Inventory](https://github.com/NobUwU/BeRP/blob/main/docs/player.md)* -------------------------------------------------------------------------------- /packets.txt: -------------------------------------------------------------------------------- 1 | Some Of The "bounds" may be incorrect 2 | 3 | S: login 4 | C: play_status 5 | C: server_to_client_handshake 6 | S: client_to_server_handshake 7 | C: disconnect 8 | C: resource_packs_info 9 | C: resource_pack_stack 10 | S: resource_pack_client_response 11 | B: text 12 | C: set_time 13 | C: start_game 14 | C: add_player 15 | C: add_entity 16 | C: remove_entity 17 | C: add_item_entity 18 | C: take_item_entity 19 | B: move_entity 20 | B: move_player 21 | B: rider_jump 22 | C: update_block 23 | C: add_painting 24 | B: tick_sync 25 | B: level_sound_event_old 26 | C: level_event 27 | C: block_event 28 | B: entity_event 29 | C: mob_effect 30 | C: update_attributes 31 | B: inventory_transaction 32 | B: mob_equipment 33 | B: mob_armor_equipment 34 | B: interact 35 | S: block_pick_request 36 | S: entity_pick_request 37 | S: player_action 38 | C: hurt_armor 39 | B: set_entity_data 40 | B: set_entity_motion 41 | C: set_entity_link 42 | C: set_health 43 | C: set_spawn_position 44 | B: animate 45 | B: respawn 46 | C: container_open 47 | B: container_close 48 | B: player_hotbar 49 | B: inventory_content 50 | B: inventory_slot 51 | C: container_set_data 52 | C: crafting_data 53 | B: crafting_event 54 | C: gui_data_pick_item 55 | B: adventure_settings 56 | B: block_entity_data 57 | S: player_input 58 | C: level_chunk 59 | C: set_commands_enabled 60 | C: set_difficulty 61 | C: change_dimension 62 | B: set_player_game_type 63 | C: player_list 64 | C: simple_event 65 | C: event 66 | C: spawn_experience_orb 67 | C: clientbound_map_item_data 68 | B: map_info_request 69 | B: request_chunk_radius 70 | C: chunk_radius_update 71 | B: item_frame_drop_item 72 | C: game_rules_changed 73 | C: camera 74 | B: boss_event 75 | C: show_credits 76 | C: available_commands 77 | S: command_request 78 | S: command_block_update 79 | C: command_output 80 | C: update_trade 81 | C: update_equipment 82 | C: resource_pack_data_info 83 | C: resource_pack_chunk_data 84 | S: resource_pack_chunk_request 85 | C: transfer 86 | C: play_sound 87 | C: stop_sound 88 | C: set_title 89 | C: add_behavior_tree 90 | C: structure_block_update 91 | C: show_store_offer 92 | S: purchase_receipt 93 | B: player_skin 94 | C: sub_client_login 95 | C: initiate_web_socket_connection 96 | C: set_last_hurt_by 97 | S: book_edit 98 | B: npc_request 99 | S: photo_transfer 100 | C: modal_form_request 101 | S: modal_form_response 102 | S: server_settings_request 103 | C: server_settings_response 104 | C: show_profile 105 | C: set_default_game_type 106 | C: remove_objective 107 | C: set_display_objective 108 | C: set_score 109 | B: lab_table 110 | C: update_block_synced 111 | C: move_entity_delta 112 | C: set_scoreboard_identity 113 | S: set_local_player_as_initialized 114 | C: update_soft_enum 115 | B: network_stack_latency 116 | B: script_custom_event 117 | C: spawn_particle_effect 118 | C: available_entity_identifiers 119 | B: level_sound_event_v2 120 | C: network_chunk_publisher_update 121 | C: biome_definition_list 122 | B: level_sound_event 123 | C: level_event_generic 124 | C: lectern_update 125 | C: video_stream_connect 126 | C: add_ecs_entity 127 | C: remove_ecs_entity 128 | B: client_cache_status 129 | C: on_screen_texture_animation 130 | C: map_create_locked_copy 131 | C: structure_template_data_export_request 132 | C: structure_template_data_export_response 133 | C: update_block_properties 134 | C: client_cache_blob_status 135 | C: client_cache_miss_response 136 | C: education_settings 137 | S: multiplayer_settings 138 | S: settings_command 139 | S: anvil_damage 140 | C: completed_using_item 141 | B: network_settings 142 | S: player_auth_input 143 | C: creative_content 144 | C: player_enchant_options 145 | S: item_stack_request 146 | C: item_stack_response 147 | C: player_armor_damage 148 | S: update_player_game_type 149 | S: position_tracking_db_request 150 | C: position_tracking_db_broadcast 151 | S: packet_violation_warning 152 | C: motion_prediction_hints 153 | C: animate_entity 154 | C: camera_shake 155 | C: player_fog 156 | C: correct_player_move_prediction 157 | C: item_component 158 | B: filter_text_packet 159 | C: debug_renderer 160 | C: sync_entity_property 161 | C: add_volume_entity 162 | C: remove_volume_entity 163 | C: simulation_type 164 | C: npc_dialogue -------------------------------------------------------------------------------- /src/berp/http/SequentialBucket.ts: -------------------------------------------------------------------------------- 1 | import { Request } from './Request' 2 | import { Logger } from '../../console' 3 | 4 | export class SequentialBucket { 5 | private _runtimeId = 0n 6 | private _bucket = new Map() 7 | private _failedBucket = new Map() 8 | private _logger: Logger 9 | private _reqInt: number 10 | private _requestPool: NodeJS.Timer 11 | private _flushPaused = true 12 | private _debug: boolean 13 | constructor(interval?: number, logger?: Logger, debug = false) { 14 | this._reqInt = interval || 10 15 | this._logger = logger || new Logger(`Sequential Bucket ${Date.now().toString() 16 | .substring(7)}`, "#8769ff") 17 | this._debug = debug 18 | this.resumeFlush() 19 | 20 | this._logger.success("Bucket Initialized") 21 | } 22 | public getLogger(): Logger { return this._logger } 23 | public getRuntimeId(): bigint { return this._runtimeId } 24 | public getBucket(): Map { return this._bucket } 25 | public getFailedBucket(): Map { return this._failedBucket } 26 | 27 | public resumeFlush(): void { 28 | if (this._flushPaused) { 29 | this._requestPool = setInterval(() => { 30 | if (this._bucket.size > 0) { 31 | const [id, req] = Array.from(this._bucket.entries())[0] 32 | if (id) { 33 | this._bucket.delete(id) 34 | this._attemptRequest(id, req) 35 | } 36 | } 37 | }, this._reqInt) 38 | this._flushPaused = false 39 | } 40 | } 41 | public pauseFlush(): void { 42 | if (!this._flushPaused) { 43 | clearInterval(this._requestPool) 44 | this._flushPaused = true 45 | } 46 | } 47 | 48 | public addRequest(r: Request): bigint { 49 | this._runtimeId++ 50 | this._bucket.set(this._runtimeId, r) 51 | 52 | return this._runtimeId 53 | } 54 | public removeRequest(runtimeId: bigint): Request { 55 | const r = this._bucket.get(runtimeId) 56 | this._bucket.delete(runtimeId) 57 | 58 | return r 59 | } 60 | public emptyBucket(): void { 61 | const requests = this._bucket.size 62 | this._logger.warn(`Emptied bucket.`, requests, "request(s) disposed.") 63 | this._bucket = new Map() 64 | } 65 | public emptyFailedBucket(): void { 66 | const requests = this._bucket.size 67 | this._logger.warn(`Emptied failed bucket.`, requests, "request(s) disposed.") 68 | this._failedBucket = new Map() 69 | } 70 | 71 | private _attemptRequest(id: bigint, r: Request, attempt?: number): void { 72 | try { 73 | r.makeRequest() 74 | .then((res) => { 75 | try { 76 | r.onFufilled(res) 77 | } catch (error) { 78 | if (this._debug) { 79 | this._logger.error("Attempted to call request \"onFulfilled\" method, but recieved error", error) 80 | this._failedBucket.set(id, r) 81 | } 82 | } 83 | }) 84 | .catch((error) => { 85 | if (r.getRequestOptions().attempts - 1 > (attempt ? attempt : 0)) { 86 | if (this._debug) this._logger.warn("Error occured when attempting to make request. Attempting request again in", r.getRequestOptions().attemptTimeout + "ms") 87 | setTimeout(() => { 88 | this._attemptRequest(id, r, attempt ? attempt += 1 : 1) 89 | }, r.getRequestOptions().attemptTimeout || 2000) 90 | } else { 91 | if (this._debug) this._logger.error("Failed to make request after", r.getRequestOptions().attempts, "attempts... Disposing request.") 92 | this._failedBucket.set(id, r) 93 | try { 94 | r.onFailed(error) 95 | } catch (error) { 96 | if (this._debug) { 97 | this._logger.error("Attempted to call request \"onFailed\" method, but recieved error", error) 98 | } 99 | } 100 | } 101 | }) 102 | } catch (error) { 103 | this._logger.error("Failed to make request during flush... Disposing request\n", error) 104 | this._failedBucket.set(id, r) 105 | try { 106 | r.onFailed(error) 107 | } catch (error) { 108 | if (this._debug) { 109 | this._logger.error("Attempted to call request \"onFailed\" method, but recieved error", error) 110 | } 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/command/CommandManager.ts: -------------------------------------------------------------------------------- 1 | import { BeRP } from '../../../' 2 | import { ConnectionHandler } from 'src/berp/network' 3 | import { PluginApi } from '../pluginApi' 4 | import { v4 as uuidv4 } from 'uuid' 5 | import { packet_command_output } from 'src/types/packets.i' 6 | import { 7 | CommandOptions, 8 | CommandResponse, 9 | ConsoleCommandOptions, 10 | } from 'src/types/berp' 11 | import { 12 | CUR_VERSION_PROTOCOL, 13 | CUR_VERSION, 14 | BeRP_VERSION, 15 | } from '../../../../Constants' 16 | import { ConsoleCommand } from './ConsoleCommand' 17 | 18 | export class CommandManager { 19 | private _berp: BeRP 20 | private _connection: ConnectionHandler 21 | private _pluginApi: PluginApi 22 | private _requests = new Map() 23 | private _commands = new Map() 24 | public enabled = true 25 | constructor(berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 26 | this._berp = berp 27 | this._connection = connection 28 | this._pluginApi = pluginApi 29 | } 30 | public async onEnabled(): Promise { 31 | this._connection.on('command_output', (packet) => { 32 | if (!packet) return 33 | if (!this._requests.has(packet.origin.uuid)) return 34 | this._requests.get(packet.origin.uuid).execute(packet) 35 | }) 36 | this._pluginApi.getEventManager().on('ChatCommand', async (data) => { 37 | if (this._pluginApi.getPluginId() != 1 || !this.enabled) return 38 | this._berp.getCommandManager().executeCommand(data) 39 | this._connection.sendCommandFeedback(false) 40 | }) 41 | this._defaultCommands() 42 | 43 | return 44 | } 45 | public async onDisabled(): Promise { 46 | for (const [, command] of this._commands) { 47 | if (command.command.command == "help" || command.command.command == "about") continue 48 | this._berp.getCommandManager().unregisterCommand(command.command, command.type) 49 | } 50 | } 51 | private _defaultCommands(): void { 52 | this.registerCommand({ 53 | command: 'help', 54 | description: `Displays a list of available commands.`, 55 | aliases: ['h'], 56 | }, (data) => { 57 | data.sender.sendMessage(`§bShowing all Available Commands:`) 58 | this._berp.getCommandManager().getCommands() 59 | .forEach((command) => { 60 | if (!command.showInList) return 61 | data.sender.sendMessage(` §7${this._berp.getCommandManager().getPrefix()}${command.options.command}§r §o§8- ${command.options.description}§r`) 62 | }) 63 | }) 64 | this.registerCommand({ 65 | 'command': 'about', 66 | 'description': 'Shows info about the server.', 67 | 'aliases': ['ab'], 68 | }, (data) => { 69 | data.sender.sendMessage(`§7This server is running §9BeRP v${BeRP_VERSION}§7 for §aMinecraft: Bedrock Edition v${CUR_VERSION} §7(§a${CUR_VERSION_PROTOCOL}§7).`) 70 | }) 71 | } 72 | public executeCommand(command: string, callback?: (res: packet_command_output) => void): void { 73 | if (command.startsWith('say' || 'tellraw' || 'me' || 'titleraw')) callback = undefined 74 | const requestId = uuidv4() 75 | if (callback) { 76 | this._connection.sendCommandFeedback(true) 77 | this._requests.set(requestId, { execute: callback }) 78 | } 79 | this._connection.sendPacket('command_request', { 80 | command: command, 81 | interval: false, 82 | origin: { 83 | uuid: requestId, 84 | request_id: requestId, 85 | type: 'player', 86 | }, 87 | }) 88 | } 89 | public registerCommand(options: CommandOptions, callback: (data: CommandResponse) => void): void { 90 | this._commands.set(`${options.command}:game`, { 91 | type: 'game', 92 | command: options, 93 | }) 94 | this._berp.getCommandManager().registerCommand(options, (data) => { 95 | callback(data) 96 | }) 97 | } 98 | public registerConsoleCommand(options: ConsoleCommandOptions, callback: (args: string[]) => void): void { 99 | this._commands.set(`${options.command}:console`, { 100 | type: 'console', 101 | command: options, 102 | }) 103 | const command = new ConsoleCommand(options, callback) 104 | this._berp.getCommandHandler().registerCommand(command) 105 | } 106 | public getPrefix(): string { return this._berp.getCommandManager().getPrefix() } 107 | public setPrefix(prefix: string): void { this._berp.getCommandManager().setPrefix(prefix) } 108 | } 109 | -------------------------------------------------------------------------------- /src/berp/utils/compileProtocol.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import { 3 | ProtoDefYAMLParse, 4 | ProtoDefYAMLCompile, 5 | getFiles, 6 | McCompiler, 7 | } from './' 8 | import { 9 | ProtoDataPath, 10 | CUR_VERSION, 11 | } from '../../Constants' 12 | import { Compiler } from 'protodef' 13 | const { ProtoDefCompiler } = Compiler 14 | import { resolve } from 'path' 15 | import fs from 'fs' 16 | import { Logger } from '../../console' 17 | 18 | const latest = resolve(ProtoDataPath, 'latest') 19 | 20 | function getJSON(path: string): any { 21 | return JSON.parse(fs.readFileSync(path, 'utf-8')) 22 | } 23 | 24 | function genProtoSchema(): string { 25 | const parsed = ProtoDefYAMLParse(resolve(latest, 'proto.yml')) 26 | const version: string = parsed['!version'] 27 | const packets = [] 28 | for (const key in parsed) { 29 | if (key.startsWith('%container')) { 30 | const [, name] = key.split(",") 31 | if (name.startsWith('packet_')) { 32 | const children = parsed[key] 33 | const packetName = name.replace('packet_', '') 34 | const packetID = children['!id'] 35 | packets.push([packetID, packetName, name]) 36 | } 37 | } 38 | } 39 | let l1 = '' 40 | let l2 = '' 41 | for (const [id, name, fname] of packets) { 42 | l1 += ` 0x${id.toString(16).padStart(2, '0')}: ${name}\n` 43 | l2 += ` if ${name}: ${fname}\n` 44 | } 45 | const t = `#Auto-generated from proto.yml, do not modify\n!import: types.yaml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}` 46 | fs.writeFileSync(resolve(latest, 'packet_map.yml'), t) 47 | 48 | ProtoDefYAMLCompile(resolve(latest, 'proto.yml'), resolve(latest, 'proto.json')) 49 | 50 | return version 51 | } 52 | 53 | function copyData(): string { 54 | const version = genProtoSchema() 55 | fs.mkdirSync(resolve(ProtoDataPath, version), { recursive: true }) 56 | fs.writeFileSync(resolve(ProtoDataPath, `${version}/protocol.json`), JSON.stringify({ types: getJSON(resolve(latest, 'proto.json')) }, null, 2)) 57 | fs.unlinkSync(resolve(latest, 'proto.json')) // remove temp file 58 | fs.unlinkSync(resolve(latest, 'packet_map.yml')) // remove temp file 59 | 60 | return version 61 | } 62 | 63 | function createProtocol(ver: string): void { 64 | const compiler = new ProtoDefCompiler() 65 | 66 | const path = resolve(ProtoDataPath, ver) 67 | 68 | const protocol = getJSON(resolve(path, 'protocol.json')).types 69 | compiler.addTypes(eval(McCompiler)) 70 | compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) 71 | compiler.addTypesToCompile(protocol) 72 | 73 | fs.writeFileSync(resolve(path, 'read.js'), 'module.exports = ' + compiler.readCompiler.generate().replace('() =>', 'native =>')) 74 | fs.writeFileSync(resolve(path, 'write.js'), 'module.exports = ' + compiler.writeCompiler.generate().replace('() =>', 'native =>')) 75 | fs.writeFileSync(resolve(path, 'size.js'), 'module.exports = ' + compiler.sizeOfCompiler.generate().replace('() =>', 'native =>')) 76 | 77 | compiler.compileProtoDefSync() 78 | } 79 | 80 | function copyExtra(ver: string): void { 81 | try { 82 | const files = getFiles(latest) 83 | const ignoreFiles = ["proto.yml","types.yaml"] 84 | 85 | for (const file of files) { 86 | const splitFilePath = file.split(/(\/|\\)/) 87 | const fileName = splitFilePath[splitFilePath.length - 1] 88 | if (!ignoreFiles.includes(fileName)) { 89 | fs.copyFileSync(file, resolve(ProtoDataPath, ver, fileName)) 90 | } 91 | } 92 | 93 | } catch {} 94 | } 95 | 96 | function genData(): string { 97 | const version = copyData() 98 | createProtocol(version) 99 | copyExtra(version) 100 | 101 | return version 102 | } 103 | 104 | const protoLogger = new Logger('Protocol Compiler', 'red') 105 | export function AttemptProtocolCompiler(): void { 106 | if (!fs.existsSync(resolve(ProtoDataPath, CUR_VERSION)) 107 | || !fs.existsSync(resolve(ProtoDataPath, CUR_VERSION, 'protocol.json')) 108 | || !fs.existsSync(resolve(ProtoDataPath, CUR_VERSION, 'read.js')) 109 | || !fs.existsSync(resolve(ProtoDataPath, CUR_VERSION, 'write.js')) 110 | || !fs.existsSync(resolve(ProtoDataPath, CUR_VERSION, 'size.js')) 111 | || !fs.existsSync(resolve(ProtoDataPath, CUR_VERSION, 'steve.json')) 112 | || !fs.existsSync(resolve(ProtoDataPath, CUR_VERSION, 'steveGeometry.json')) 113 | || !fs.existsSync(resolve(ProtoDataPath, CUR_VERSION, 'steveSkin.bin'))) { 114 | protoLogger.info("Proto data missing, starting data gen...") 115 | const version = genData() 116 | protoLogger.success("Generated", version, "protocol data") 117 | } else { 118 | protoLogger.info("Proto data detected, skipping compiler. Use \"recompile\" to recompile the proto def's!") 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/berp/auth/AuthHandler.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PublicClientApplication, 3 | Configuration, 4 | DeviceCodeRequest, 5 | AuthenticationResult, 6 | TokenCache, 7 | SilentFlowRequest, 8 | } from '@azure/msal-node' 9 | import { 10 | AuthHandlerOptions, 11 | AuthHandlerXSTSResponse, 12 | } from '../../types/berp' 13 | import { CachePlugin } from './CachePlugin' 14 | import { resolve } from 'path' 15 | import { 16 | Logger, 17 | } from '../../console' 18 | import fs from 'fs' 19 | import * as XBLAuth from '@xboxreplay/xboxlive-auth' 20 | import * as Constants from '../../Constants' 21 | 22 | class AuthHandler { 23 | private _options: AuthHandlerOptions 24 | private _msalApp: PublicClientApplication 25 | private _logger = new Logger("Auth Handler", 'green') 26 | constructor(options: AuthHandlerOptions) { 27 | if (!options) throw new Error("Invalid AuthHandler Options") 28 | this._options = options 29 | 30 | fs.mkdirSync(options.cacheDir, { recursive: true }) 31 | this._logger.success("Auth Provider Prepared") 32 | } 33 | public getLogger(): Logger { return this._logger } 34 | 35 | public createConfig(): Configuration { 36 | return { 37 | auth: { 38 | clientId: this._options.clientId, 39 | authority: this._options.authority, 40 | }, 41 | cache: { 42 | cachePlugin: CachePlugin(resolve(this._options.cacheDir, "cache.json")), 43 | }, 44 | } 45 | } 46 | public createApp(config: Configuration): void { 47 | this._msalApp = new PublicClientApplication(config) 48 | this._logger.success("Auth Provider App Created") 49 | } 50 | /** 51 | * Creates Auth Link For User To Auth With 52 | * @returns {Promise} Authenticated User Info 53 | */ 54 | public createDeviceOauthGrant(request: DeviceCodeRequest): Promise { 55 | return this._msalApp.acquireTokenByDeviceCode(request) 56 | } 57 | /** 58 | * Auths a user via username and password. As far as I know this is not multi-factor compatible 59 | * @param username Username For Account 60 | * @param password Password For Account 61 | * @param scopes Access Scopes 62 | * @returns {Promise} Authenticated User Info 63 | */ 64 | public authByUsernameAndPass(username: string, password: string, scopes: string[]): Promise { 65 | return this._msalApp.acquireTokenByUsernamePassword({ 66 | username, 67 | password, 68 | scopes, 69 | }) 70 | } 71 | /** 72 | * Gets User Cache 73 | */ 74 | public getCache(): TokenCache { 75 | return this._msalApp.getTokenCache() 76 | } 77 | /** 78 | * Attempts to get token from cache 79 | * 80 | * Handles refreshing automatically 81 | */ 82 | public aquireTokenFromCache(request: SilentFlowRequest): Promise { 83 | return this._msalApp.acquireTokenSilent(request) 84 | } 85 | /** 86 | * Exchange RPS for XBL User Token 87 | * @param rps MSAL Access Token 88 | * @param authTitle Defaults true, dont make false 89 | */ 90 | public exchangeRpsForUserToken(rps: string, authTitle?: boolean): Promise { 91 | authTitle = authTitle || true 92 | 93 | return XBLAuth.exchangeRpsTicketForUserToken((authTitle ? 'd=' : 't=') + rps) 94 | } 95 | /** 96 | * Excahnge User Token for XSTS 97 | * @param token User Token 98 | * @param relyingParty URL Enpoint in which this token will be used 99 | */ 100 | public exchangeUserTokenForXSTS(token: string, relyingParty: string): Promise { 101 | return XBLAuth.exchangeUserTokenForXSTSIdentity(token, { 102 | raw: false, 103 | XSTSRelyingParty: relyingParty, 104 | }) as Promise 105 | } 106 | /** 107 | * Gets XSTS Credentials To Use Realm API 108 | */ 109 | public async ezXSTSForRealmAPI(user: AuthenticationResult): Promise { 110 | const userToken = await this.exchangeRpsForUserToken(user.accessToken) 111 | const XSTSIdentity = await this.exchangeUserTokenForXSTS(userToken.Token, Constants.Endpoints.Authorities.RealmAPI) 112 | 113 | return { 114 | name: user.account.name, 115 | hash: XSTSIdentity.userHash, 116 | token: XSTSIdentity.XSTSToken, 117 | expires: XSTSIdentity.expiresOn, 118 | } 119 | } 120 | /** 121 | * Gets XSTS Credentials To Use Raknet 122 | */ 123 | public async ezXSTSForRealmRak(user: AuthenticationResult): Promise { 124 | const userToken = await this.exchangeRpsForUserToken(user.accessToken) 125 | const XSTSIdentity = await this.exchangeUserTokenForXSTS(userToken.Token, Constants.Endpoints.Authorities.MineRak) 126 | 127 | return { 128 | name: user.account.name, 129 | hash: XSTSIdentity.userHash, 130 | token: XSTSIdentity.XSTSToken, 131 | expires: XSTSIdentity.expiresOn, 132 | } 133 | } 134 | } 135 | export { AuthHandler } 136 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/socket/SocketManager.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import { BeRP } from '../../../' 3 | import { EventEmitter } from 'events' 4 | import { ConnectionHandler } from 'src/berp/network' 5 | import { PluginApi } from '../pluginApi' 6 | import { 7 | JsonData, 8 | JsonRequest, 9 | RawText, 10 | } from 'src/types/berp' 11 | import { 12 | SocketOutboundValues, 13 | SocketValues, 14 | } from 'src/types/pluginApi.i' 15 | import { defaultRequests } from './requests/index' 16 | import { v4 as uuidv4 } from 'uuid' 17 | 18 | export interface SocketManager { 19 | on(event: K, callback: (...args: SocketValues[K]) => void): this 20 | on( 21 | event: Exclude, 22 | callback: (...args: unknown[]) => void, 23 | ): this 24 | once(event: K, callback: (...args: SocketValues[K]) => void): this 25 | once( 26 | event: Exclude, 27 | callback: (...args: unknown[]) => void, 28 | ): this 29 | emit(event: K, ...args: SocketValues[K]): boolean 30 | emit( 31 | event: Exclude, 32 | ...args: unknown[] 33 | ): boolean 34 | sendMessage(options: JsonRequest, callback?: (data: JsonData) => void): void 35 | getHeartbeats(): number 36 | newUUID(): string 37 | } 38 | 39 | export class SocketManager extends EventEmitter { 40 | private _berp: BeRP 41 | private _connection: ConnectionHandler 42 | private _pluginApi: PluginApi 43 | private _requests = new Map() 44 | private _defaultRequests = new Map() 45 | public _api: string 46 | public _verison: string 47 | public _mcbe: string 48 | public _protocol: number 49 | public enabled: boolean 50 | constructor(berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 51 | super() 52 | this._berp = berp 53 | this._connection = connection 54 | this._pluginApi = pluginApi 55 | this.enabled = false 56 | } 57 | public async onEnabled(): Promise { 58 | this._listener() 59 | this._loadRequests() 60 | setTimeout(() => this._pluginApi.getCommandManager().executeCommand('tag @s add "berpUser"'), 3500) 61 | } 62 | public async onDisabled(): Promise { 63 | for (const [, request] of this._defaultRequests) { 64 | request.onDisabled() 65 | } 66 | } 67 | private _listener(): void { 68 | this._connection.on('text', (packet) => { 69 | try { 70 | if (packet.type !== 'json_whisper') return 71 | const parsedMessage: RawText = JSON.parse(packet.message) 72 | if (!parsedMessage.rawtext[0].text.startsWith('{"berp":')) return 73 | const message = [] 74 | for (const raw of parsedMessage.rawtext) { 75 | message.push(raw.text) 76 | } 77 | const data = JSON.parse(message.join('').replace(/\n/g, "\\n")) 78 | if (this._requests.has(`${data.berp.requestId}:${data.berp.event}`)) { 79 | this._requests.get(`${data.berp.requestId}:${data.berp.event}`).execute(data.berp) 80 | this._requests.delete(`${data.berp.requestId}:${data.berp.event}`) 81 | } 82 | 83 | return this.emit('Message', data.berp) 84 | } catch (err) { 85 | console.log(packet.message) 86 | console.log(`caught: ${err}`) 87 | } 88 | }) 89 | } 90 | private _loadRequests(): void { 91 | for (const request of defaultRequests) { 92 | const newRequest = new request(this, this._berp, this._connection, this._pluginApi) 93 | newRequest.onEnabled() 94 | this._defaultRequests.set(newRequest.requestName, newRequest) 95 | } 96 | } 97 | public sendMessage(options: JsonRequest, callback?: (data: JsonData) => void): void { 98 | if (callback) this._requests.set(`${options.berp.requestId}:${options.berp.event}`, { execute: callback }) 99 | this._connection.sendPacket('text', { 100 | message: JSON.stringify(options), 101 | needs_translation: false, 102 | platform_chat_id: '', 103 | source_name: this._connection.getXboxProfile().extraData.displayName, 104 | type: 'chat', 105 | xuid: this._connection.getXboxProfile().extraData.XUID, 106 | }) 107 | } 108 | // @ts-ignore 109 | public sendPacket(name: K, params: SocketOutboundValues[K][0], callback?: (data: SocketValues[K][0]) => void): void { 110 | const requestId = this.newUUID() 111 | this.sendMessage({ 112 | berp: { 113 | event: name, 114 | requestId: requestId, 115 | ...params, 116 | }, 117 | }, (res) => { 118 | if (!callback) return 119 | 120 | return callback(res as any) 121 | }) 122 | } 123 | public getHeartbeats(): number { return this._defaultRequests.get('Heartbeat').getTotalBeats() } 124 | public newUUID(): string { return uuidv4() } 125 | } 126 | -------------------------------------------------------------------------------- /src/console/commands/account.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from "./base/BaseCommand" 2 | import { BeRP } from "../../berp" 3 | import chalk from "chalk" 4 | import * as Constants from '../../Constants' 5 | import readline from 'readline' 6 | import { DeviceCodeRequest } from "@azure/msal-node" 7 | export class Account extends BaseCommand { 8 | private _berp: BeRP 9 | public name = "account" 10 | public description = "Microsoft account manager." 11 | public usage = "" 12 | public aliases = [ 13 | "acc", 14 | ] 15 | constructor(berp: BeRP) { 16 | super() 17 | this._berp = berp 18 | } 19 | public async execute(args: string[]): Promise { 20 | if (!args[0] || !["add","list","remove"].includes(args[0].toLowerCase())) { 21 | return this._berp.getCommandHandler().error(`Invalid argument at "${this.name} >>${args[0] || " "}<<".`) 22 | } 23 | switch(args[0].toLowerCase()) { 24 | case "list": 25 | this._listAccounts() 26 | break 27 | case "add": 28 | this._addAccount() 29 | break 30 | case "remove": 31 | this._removeAccount() 32 | break 33 | } 34 | } 35 | private async _listAccounts(): Promise { 36 | const accounts = await this._berp 37 | .getAuthProvider() 38 | .getCache() 39 | .getAllAccounts() 40 | 41 | if (accounts.length) { 42 | console.log(`${chalk.blueBright('Active BeRP Session - accounts - list:')}\n${accounts.map(i => `${chalk.gray(` - ${i.name} (${i.username})`)}`).join('\n')}`) 43 | } else { 44 | console.log(`${chalk.blueBright('Active BeRP Session - accounts - list:')}\n ${chalk.red("No current account sessions. Try adding an account with \"account add\"")}`) 45 | } 46 | } 47 | private _addAccount(): void { 48 | this._berp.getConsole().stop() 49 | 50 | const deviceCodeRequest: DeviceCodeRequest = { 51 | scopes: Constants.Scopes, 52 | deviceCodeCallback: (device) => { 53 | console.log(chalk.blueBright(`BeRP Microsoft Account Link:\n${chalk.grey(`- Navigate to ${chalk.cyan(device.verificationUri)}.`)}\n${chalk.grey(`- Use the code ${chalk.cyan(device.userCode)} to link your account.`)}`)) 54 | }, 55 | } 56 | 57 | let tempConsole = readline.createInterface({ 58 | input: process.stdin, 59 | output: process.stdout, 60 | prompt: "", 61 | }) 62 | const disposeTempConsole = (): void => { 63 | if (tempConsole) { 64 | tempConsole.removeAllListeners('line') 65 | tempConsole.close() 66 | tempConsole = undefined 67 | this._berp.getConsole().start() 68 | } 69 | } 70 | tempConsole.on('line', (l) => { 71 | if (l.toLowerCase() === 'cancel') { 72 | this._berp.getCommandHandler().error("Canceled BeRP Microsoft Account Link") 73 | disposeTempConsole() 74 | deviceCodeRequest.cancel = true 75 | } 76 | }) 77 | 78 | this._berp.getLogger().info("Attempting oauth device grant please follow all instructions given below or type \"cancel\" to quit!") 79 | this._berp.getAuthProvider() 80 | .createDeviceOauthGrant(deviceCodeRequest) 81 | .then(res => { 82 | disposeTempConsole() 83 | this._berp.getAuthProvider() 84 | .getLogger() 85 | .success("Successfully added new account", `${res.account.name} (${res.account.username})`) 86 | }) 87 | .catch(err => { 88 | if (!deviceCodeRequest.cancel) { 89 | disposeTempConsole() 90 | this._berp.getAuthProvider() 91 | .getLogger() 92 | .error("Failed to add new account...\n", err) 93 | } 94 | }) 95 | } 96 | private async _removeAccount(): Promise { 97 | const accounts = await this._berp.getAuthProvider() 98 | .getCache() 99 | .getAllAccounts() 100 | 101 | if (!accounts) return this._berp.getCommandHandler().error("There are no active accounts linked to BeRP!") 102 | 103 | this._berp.getConsole() 104 | .sendSelectPrompt("Select which account you would like to remove", accounts.map(a => `${a.name} (${a.username})`)) 105 | .then((r) => { 106 | if (r) { 107 | const username = /\(.*\)/.exec(r)[0].replace(/(\(|\))/g, "") 108 | const account = accounts.find(a => a.username === username) 109 | if (!account) return this._berp.getAuthProvider() 110 | .getLogger() 111 | .error(`Failed to remove account "${username}"`) 112 | 113 | this._berp.getAuthProvider() 114 | .getCache() 115 | .removeAccount(account) 116 | .then(() => { 117 | this._berp.getAuthProvider() 118 | .getLogger() 119 | .success(`Successfully removed account "${username}"`) 120 | }) 121 | .catch((e) => { 122 | this._berp.getAuthProvider() 123 | .getLogger() 124 | .error(`Failed to remove account "${username}"...\n`, e) 125 | }) 126 | } 127 | }) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Constants.ts: -------------------------------------------------------------------------------- 1 | import { MCHeaders } from "./types/berp" 2 | import { resolve } from 'path' 3 | 4 | export const ProtoDataPath = resolve(process.cwd(), 'data') 5 | export const CUR_VERSION = '1.19.20' 6 | export const CUR_VERSION_PROTOCOL = 544 7 | export const BeRP_VERSION = '1.0.0' 8 | 9 | const MCRAPI = "https://pocket.realms.minecraft.net/" 10 | 11 | export const Endpoints = { 12 | Authorities: { 13 | RealmAPI: MCRAPI, 14 | MineRak: "https://multiplayer.minecraft.net/", 15 | ClubHub: "https://clubhub.xboxlive.com/", 16 | PeopleHub: "https://peoplehub.xboxlive.com/", 17 | MSAL: "https://login.microsoftonline.com/consumers", 18 | }, 19 | Misc: { 20 | MinecraftAuth: 'https://multiplayer.minecraft.net/authentication', 21 | XboxDeviceAuth: 'https://device.auth.xboxlive.com/device/authenticate', 22 | XboxTitleAuth: 'https://title.auth.xboxlive.com/title/authenticate', 23 | XstsAuthorize: 'https://xsts.auth.xboxlive.com/xsts/authorize', 24 | LiveDeviceCodeRequest: 'https://login.live.com/oauth20_connect.srf', 25 | LiveTokenRequest: 'https://login.live.com/oauth20_token.srf', 26 | }, 27 | RealmAPI: { 28 | GET: { 29 | UserCompatible: MCRAPI + "mco/client/compatible", 30 | UserTrial: MCRAPI + "trial/new", 31 | UserInvites: "invites/count/pending", 32 | LivePlayers: MCRAPI + "activities/live/players", 33 | Realms: MCRAPI + "worlds", 34 | Realm: (id: number): string => MCRAPI + `worlds/${id}`, 35 | RealmJoinInfo: (id: number): string => MCRAPI + `worlds/${id}/join`, 36 | RealmPacks: (id: number): string => MCRAPI + `worlds/${id}/content`, 37 | RealmSubsciptionDetails: (id: number): string => MCRAPI + `subscriptions/${id}/details`, 38 | RealmBackups: (id: number): string => MCRAPI + `worlds/${id}/backups`, 39 | RealmBackup: (id: number, slot: number, backupId: string): string => MCRAPI + `archive/download/world/${id}/${slot}/${backupId}`, 40 | RealmBackupLatest: (id: number, slot: number): string => MCRAPI + `archive/download/world/${id}/${slot}/latest`, 41 | RealmInviteLink: (id: number): string => MCRAPI + `links/v1?worldId=${id}`, 42 | RealmByInvite: (invite: string): string => MCRAPI + `worlds/v1/link/${invite}`, 43 | RealmBlockedPlayers: (id: number): string => MCRAPI + `worlds/${id}/blocklist`, 44 | }, 45 | POST: { 46 | RealmBlockPlayer: (id: number, xuid: string | number): string => MCRAPI + `worlds/${id}/blocklist/${xuid}`, 47 | RealmAcceptInvite: (invite: string): string => MCRAPI + `worlds/v1/link/accept/${invite}`, 48 | RealmConfiguration: (id: number): string => MCRAPI + `worlds/${id}/configuration`, 49 | }, 50 | PUT: { 51 | RealmUpdateInvite: (id: number): string => MCRAPI + `invites/${id}/invite/update`, 52 | RealmDefaultPermission: (id: number): string => MCRAPI + `worlds/${id}/defaultPermission`, 53 | RealmUserPermission: (id: number): string => MCRAPI + `worlds/${id}/userPermission`, 54 | RealmBackup: (id: number, backupId: string): string => MCRAPI + `worlds/${id}/backups?backupId=${backupId}&clientSupportsRetries`, 55 | RealmSlot: (id: number, slotNum: number): string => MCRAPI + `worlds/${id}/slot/${slotNum}`, 56 | RealmOpen: (id: number): string => MCRAPI + `worlds/${id}/open`, 57 | RealmClose: (id: number): string => MCRAPI + `worlds/${id}/close`, 58 | }, 59 | DELETE: { 60 | RealmBlockedPlayer: (id: number, xuid: string | number): string => MCRAPI + `worlds/${id}/blocklist/${xuid}`, 61 | RealmInvite: (id: number): string => MCRAPI + `invites/${id}`, 62 | RealmWorld: (id: number): string => MCRAPI + `worlds/${id}`, 63 | }, 64 | }, 65 | } 66 | 67 | // Should Never Have To Change Unless Nobus Microsoft Account Gets Yeeted 68 | export const AzureClientID = "d4e8e17a-f8ae-47b8-a392-8b76fcdb10d2" 69 | 70 | export const Scopes = ["Xboxlive.signin", "Xboxlive.offline_access"] 71 | 72 | export const BeRPColor = "#6990ff" 73 | 74 | export const RealmAPIHeaders = (token: string): MCHeaders => { 75 | return { 76 | "cache-control": "no-cache", 77 | "Accept": "*/*", 78 | "Accept-Encoding": "gzip, deflate, br", 79 | "Accept-Language": "en-US,en;q=0.5", 80 | "content-type": "application/json", 81 | "charset": "utf-8", 82 | "client-version": CUR_VERSION, 83 | "authorization": token, 84 | "Connection": "Keep-Alive", 85 | "Host": "pocket.realms.minecraft.net", 86 | "User-Agent": "BeRP [Bedrock Edition Realm Protocol](https://github.com/nobuwu/berp)", 87 | } 88 | } 89 | export const MinecraftAuthHeaders = (token: string): MCHeaders => { 90 | return { 91 | "cache-control": "no-cache", 92 | "Accept": "*/*", 93 | "Accept-Encoding": "gzip, deflate, br", 94 | "Accept-Language": "en-US,en;q=0.5", 95 | "content-type": "application/json", 96 | "charset": "utf-8", 97 | "client-version": CUR_VERSION, 98 | "authorization": token, 99 | "Connection": "Keep-Alive", 100 | "Host": "multiplayer.minecraft.net", 101 | "User-Agent": "BeRP [Bedrock Edition Realm Protocol](https://github.com/nobuwu/berp)", 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/berp/raknet/PacketHandler.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { 3 | types, 4 | Serializer, 5 | } from 'protodef' 6 | import { 7 | Parser, 8 | createDeserializer, 9 | createSerializer, 10 | } from './Serializer' 11 | import zlib from 'zlib' 12 | const [readVarInt, writeVarInt, sizeOfVarInt] = types.varint 13 | import { Encryption } from './Encryption' 14 | 15 | export class PacketHandler { 16 | private serializer: Serializer 17 | private deserializer: Parser 18 | private encryptor: Encryption 19 | private encryptionStarted = false 20 | constructor() { 21 | this.serializer = createSerializer() 22 | this.deserializer = createDeserializer() 23 | } 24 | public getSerializer(): Serializer { return this.serializer } 25 | public getDeserializer(): Parser { return this.deserializer } 26 | public getEncryptor(): Encryption { return this.encryptor } 27 | 28 | public startEncryption(iv: Buffer, secretKeyBytes: Buffer): void { 29 | this.encryptor = new Encryption(iv, secretKeyBytes) 30 | this.encryptionStarted = true 31 | } 32 | public async createPacket(name: string, params: { [k: string]: any }): Promise { 33 | try { 34 | const pak = this.serializer.createPacketBuffer({ 35 | name, 36 | params, 37 | }) as Buffer 38 | if (this.encryptionStarted) { 39 | return await this._handleWriteEPak(pak) 40 | } else { 41 | return Promise.resolve(this._handleWriteUPak(pak)) 42 | } 43 | } catch (error) { 44 | throw error 45 | } 46 | } 47 | public async readPacket(buffer: Buffer): Promise<{ name: string, params: unknown }[]> { 48 | try { 49 | if (buffer[0] === 0xfe) { 50 | if (this.encryptionStarted) { 51 | return await this._handleReadEPak(buffer) 52 | } else { 53 | return Promise.resolve(this._handleReadUPak(buffer)) 54 | } 55 | } 56 | } catch (error) { 57 | throw error 58 | } 59 | } 60 | public getPackets(buffer: Buffer): Buffer[] { 61 | try { 62 | const packets = [] 63 | let offset = 0 64 | while (offset < buffer.byteLength) { 65 | const { value, size } = readVarInt(buffer, offset) 66 | const dec = Buffer.allocUnsafe(value) 67 | offset += size 68 | offset += buffer.copy(dec, 0, offset, offset + value) 69 | packets.push(dec) 70 | } 71 | 72 | return packets 73 | } catch (error) { 74 | throw error 75 | } 76 | } 77 | public encode(packet: Buffer): Buffer { 78 | const def = zlib.deflateRawSync(packet, { level: 7 }) 79 | 80 | return Buffer.concat([Buffer.from([0xfe]), def]) 81 | } 82 | private _handleReadUPak(buffer: Buffer): { name: string, params: unknown }[] { 83 | try { 84 | const buf = Buffer.from(buffer) 85 | if (buf[0] !== 0xfe) throw Error('Bad batch packet header ' + buf[0]) 86 | const b = buf.slice(1) 87 | const inf = zlib.inflateRawSync(b, { chunkSize: 1024 * 1024 * 2 }) 88 | 89 | const ret: { name: string, params: unknown }[] = [] 90 | for (const packet of this.getPackets(inf)) { 91 | const des: { data: { name: string, params: unknown } } = this.deserializer.parsePacketBuffer(packet) as { data: { name: string, params: unknown } } 92 | ret.push({ 93 | name: des.data.name, 94 | params: des.data.params, 95 | }) 96 | } 97 | 98 | return ret 99 | } catch (error) { 100 | // console.log("UW OW EWWR") 101 | throw error 102 | } 103 | } 104 | private async _handleReadEPak(buffer: Buffer): Promise<{ name: string, params: unknown }[]> { 105 | try { 106 | const dpacket = await this.encryptor.createDecryptor().read(buffer.slice(1)) 107 | 108 | const ret: { name: string, params: unknown }[] = [] 109 | for (const packet of this.getPackets(dpacket)) { 110 | // console.log(packet) 111 | try { 112 | var des: { data: { name: string, params: unknown } } = this.deserializer.parsePacketBuffer(packet) as { data: { name: string, params: unknown } } // eslint-disable-line 113 | } catch (error) { 114 | // Handles broken packets being mixed with okay packets. 115 | 116 | continue 117 | } 118 | if (!des) continue 119 | else { 120 | ret.push({ 121 | name: des.data.name, 122 | params: des.data.params, 123 | }) 124 | } 125 | } 126 | 127 | return ret 128 | } catch (error) { 129 | throw error 130 | } 131 | } 132 | private _handleWriteUPak(packet: Buffer): Buffer { 133 | try { 134 | const varIntSize = sizeOfVarInt(packet.byteLength) 135 | const newPacket = Buffer.allocUnsafe(varIntSize + packet.byteLength) 136 | writeVarInt(packet.length, newPacket, 0) 137 | packet.copy(newPacket, varIntSize) 138 | 139 | return this.encode(newPacket) 140 | } catch (error) { 141 | throw error 142 | } 143 | } 144 | private async _handleWriteEPak(packet: Buffer): Promise { 145 | try { 146 | const varIntSize = sizeOfVarInt(packet.byteLength) 147 | const newPacket = Buffer.allocUnsafe(varIntSize + packet.byteLength) 148 | writeVarInt(packet.length, newPacket, 0) 149 | packet.copy(newPacket, varIntSize) 150 | const buffer = await this.encryptor.createEncryptor().create(newPacket) 151 | 152 | return Buffer.concat([Buffer.from([0xfe]), buffer]) 153 | } catch (error) { 154 | throw error 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/console/commands/connect.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RealmAPIJoinInfo, 3 | RealmAPIWorldsRes, 4 | } from "../../types/berp" 5 | import { BaseCommand } from "./base/BaseCommand" 6 | import { BeRP } from "../../berp" 7 | import * as C from '../../Constants' 8 | import { createXBLToken } from '../../berp/utils' 9 | 10 | export class Connect extends BaseCommand { 11 | private _berp: BeRP 12 | public name = "connect" 13 | public description = "Connect account to realm." 14 | public usage = "" 15 | public aliases = [ 16 | "c", 17 | ] 18 | constructor(berp: BeRP) { 19 | super() 20 | this._berp = berp 21 | } 22 | public async execute(): Promise { 23 | const accounts = await this._berp 24 | .getAuthProvider() 25 | .getCache() 26 | .getAllAccounts() 27 | 28 | if (!accounts.length) return this._berp.getCommandHandler().error("There are no active accounts linked to BeRP! Please use \"account add\" to link a new account!") 29 | 30 | this._berp.getConsole() 31 | .sendSelectPrompt("Select which account you would like to use", accounts.map(a => `${a.name} (${a.username})`)) 32 | .then(async (r) => { 33 | if (r) { 34 | try { 35 | const username = /\(.*\)/.exec(r)[0].replace(/(\(|\))/g, "") 36 | const account = accounts.find(a => a.username === username) 37 | if (!account) { 38 | return this._berp.getNetworkManager().getLogger() 39 | .error(`Failed to select account "${username}"`) 40 | } 41 | 42 | const authRes = await this._berp.getAuthProvider().aquireTokenFromCache({ 43 | scopes: C.Scopes, 44 | account, 45 | }) 46 | const xsts = await this._berp.getAuthProvider().ezXSTSForRealmAPI(authRes) 47 | 48 | let net = this._berp.getNetworkManager().getAccounts() 49 | .get(account.username) 50 | if (!net) { 51 | net = this._berp.getNetworkManager().create(account) 52 | } 53 | 54 | const curRealms = Array.from(net.getConnections().keys()) 55 | 56 | const req = new this._berp.Request({ 57 | method: "GET", 58 | url: C.Endpoints.RealmAPI.GET.Realms, 59 | headers: C.RealmAPIHeaders(createXBLToken(xsts)), 60 | }, { 61 | requestTimeout: 50000, 62 | attemptTimeout: 300, 63 | attempts: 20, 64 | }) 65 | req.onFufilled = (res: RealmAPIWorldsRes) => { 66 | if (!res.servers || !res.servers.length) return net.getLogger().error(`No realms could be found under the account "${account.username}"`) 67 | this._berp.getConsole() 68 | .sendSelectPrompt("Select which realm you would like to connect to", res.servers.filter(i => !i.expired && !curRealms.includes(i.id)).map(a => `${a.name.replace(/§\S/g, "")} (${a.id})`)) 69 | .then(async (r) => { 70 | if (r) { 71 | try { 72 | const id = /\(.*\)/.exec(r)[0].replace(/(\(|\))/g, "") 73 | const realm = res.servers.find(r => r.id === parseInt(id.replace(new RegExp(/\D/gm,'g'),''))) 74 | if (!realm) { 75 | return this._berp.getNetworkManager().getLogger() 76 | .error(`Failed to select realm`) 77 | } 78 | 79 | // console.log(createXBLToken(xsts)) 80 | 81 | const req = new this._berp.Request({ 82 | method: "GET", 83 | url: C.Endpoints.RealmAPI.GET.RealmJoinInfo(realm.id), 84 | headers: C.RealmAPIHeaders(createXBLToken(xsts)), 85 | }, { 86 | requestTimeout: 50000, 87 | attemptTimeout: 300, 88 | attempts: 100, 89 | }) 90 | req.onFufilled = (res: RealmAPIJoinInfo) => { 91 | const split = res.address.split(":") 92 | const ip = split[0] 93 | const port = parseInt(split[1]) 94 | net.newConnection(ip, port, realm) 95 | } 96 | req.onFailed = () => { 97 | return net.getLogger().error(`Failed to get join info for realm "${realm.name}"`) 98 | } 99 | this._berp.getSequentialBucket().addRequest(req) 100 | } catch (error) { 101 | this._berp.getNetworkManager().getLogger() 102 | .error(`Failed to select account for realm connection...\n`, error) 103 | } 104 | } else { 105 | this._berp.getCommandHandler().error("Connection process canceled!") 106 | } 107 | }) 108 | } 109 | req.onFailed = () => { 110 | return net.getLogger() 111 | .error(`Failed to select account for realm connection...`) 112 | } 113 | this._berp.getSequentialBucket().addRequest(req) 114 | } catch (error) { 115 | this._berp.getNetworkManager().getLogger() 116 | .error(`Failed to select account for realm connection...\n`, error) 117 | } 118 | } else { 119 | this._berp.getCommandHandler().error("Connection process canceled!") 120 | } 121 | }) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/types/berp.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Method, 3 | } from 'axios' 4 | import { ConnectionHandler } from 'src/berp/network' 5 | import { PluginApi } from 'src/berp/plugin/pluginapi/pluginApi' 6 | import { Skin } from './packetTypes.i' 7 | 8 | export type LoggerColors = ( 9 | "black" | 10 | "blackBright" | 11 | "red" | 12 | "redBright" | 13 | "green" | 14 | "greenBright" | 15 | "yellow" | 16 | "yellowBright" | 17 | "blue" | 18 | "blueBright" | 19 | "magenta" | 20 | "magentaBright" | 21 | "cyan" | 22 | "cyanBright" | 23 | "white" | 24 | "whiteBright" | 25 | "gray" | 26 | "grey" 27 | ) 28 | 29 | export interface ConsoleCommand { 30 | name: string 31 | description: string 32 | usage: string 33 | aliases: string[] 34 | new(berp) 35 | execute(argv: string[]): void 36 | } 37 | 38 | export interface MCHeaders { 39 | "cache-control": string 40 | "Accept": string 41 | "Accept-Encoding": string 42 | "Accept-Language": string 43 | "content-type": string 44 | "charset": string 45 | "client-version": string 46 | "authorization": string 47 | "Connection": string 48 | "Host": string 49 | "User-Agent": string 50 | } 51 | 52 | export type DataProviderKnownFiles = ( 53 | "protocol.json" | 54 | "steve.json" | 55 | "steveGeometry.json" | 56 | "steveSkin.bin" 57 | ) 58 | 59 | export interface AuthHandlerOptions { 60 | clientId: string 61 | authority: string 62 | cacheDir: string 63 | } 64 | export interface AuthHandlerXSTSResponse { 65 | name: string 66 | // xuid: string 67 | hash: string 68 | token: string 69 | expires: string 70 | } 71 | export interface XboxProfileExtraData { 72 | XUID: string 73 | identity: string 74 | displayName: string 75 | titleId: number 76 | } 77 | export interface XboxProfile { 78 | nbf: number 79 | extraData: XboxProfileExtraData 80 | randomNonce: number 81 | iss: string 82 | exp: number 83 | iat: number 84 | identityPublicKey: string 85 | } 86 | 87 | export interface RequestParams { 88 | method: Method 89 | url: string 90 | headers?: Record 91 | body?: Record 92 | } 93 | export interface RequestOptions { 94 | attempts?: number 95 | attemptTimeout?: number 96 | requestTimeout?: number 97 | } 98 | 99 | export interface RealmAPIJoinInfo { 100 | address: string 101 | pendingUpdate: boolean 102 | } 103 | 104 | export interface RealmAPIWorldsRes { 105 | servers: RealmAPIWorld[] 106 | } 107 | export interface RealmAPIWorld { 108 | id: number 109 | remoteSubscriptionId: string 110 | owner: string 111 | ownerUUID: string 112 | name: string 113 | motd: string 114 | defaultPermission: string 115 | state: string 116 | daysLeft: number 117 | expired: boolean 118 | expiredTrial: boolean 119 | gracePeriod: boolean 120 | worldType: string 121 | players: unknown 122 | maxPlayers: number 123 | minigameName: string 124 | minigameId: unknown 125 | minigameImage: unknown 126 | activeSlot: number 127 | slots: unknown 128 | member: boolean 129 | clubId: number 130 | subscriptionRefreshStatus: unknown 131 | } 132 | 133 | export interface examplePlugin { 134 | new (pluginApi: any) 135 | onLoaded(): Promise 136 | onEnabled(): Promise 137 | onDisabled(): Promise 138 | } 139 | 140 | export interface examplePluginConfig { 141 | name: string 142 | displayName: string 143 | color: string 144 | version: string 145 | description: string 146 | devMode: boolean 147 | main: string 148 | scripts: { 149 | build: string 150 | dev: string 151 | start: string 152 | [key: string]: string 153 | } 154 | author: string 155 | license: string 156 | dependencies: { 157 | [key: string]: string 158 | } 159 | devDependencies: { 160 | [key: string]: string 161 | } 162 | [key: string]: unknown 163 | } 164 | 165 | export interface PlayerOptions { 166 | name: string 167 | uuid: string 168 | xuid: string 169 | entityID: bigint 170 | device: number 171 | skinData: Skin 172 | } 173 | 174 | export interface EntityOptions { 175 | id: string 176 | nameTag: string 177 | runtimeId: number 178 | } 179 | 180 | export interface RawText { 181 | rawtext: Array<{text: string}> 182 | } 183 | 184 | export interface JsonRequest { 185 | berp: JsonData 186 | } 187 | 188 | export interface JsonData { 189 | event?: string 190 | sender?: any 191 | player?: any 192 | entities?: any 193 | entity?: any 194 | command?: any 195 | message?: string 196 | data?: any 197 | requestId: string 198 | } 199 | 200 | export interface Player { 201 | getName(): string 202 | getNickname(): string 203 | getRealmID(): number 204 | getUUID(): string 205 | getXuid(): string 206 | getEntityID(): bigint 207 | getDevice(): number 208 | getSkinData(): Skin 209 | getExecutionName(): string 210 | setNickname(nickname: string): void 211 | sendMessage(message: string): void 212 | executeCommand(command: string): void 213 | getTags(): Promise 214 | hasTag(tag: string): Promise 215 | getScore(objective: string): Promise 216 | kick(reason: string): void 217 | getItemCount(item: string): Promise 218 | } 219 | 220 | export interface CommandOptions { 221 | command: string 222 | aliases?: string[] 223 | description: string 224 | permissionTags?: string[] 225 | } 226 | 227 | export interface ConsoleCommandOptions { 228 | command: string 229 | aliases: string[] 230 | description: string 231 | usage: string 232 | } 233 | 234 | export interface CommandMapOptions { 235 | options: CommandOptions 236 | showInList: boolean 237 | execute(data: CommandResponse): void 238 | } 239 | 240 | export interface CommandResponse { 241 | sender: Player 242 | args: string[] 243 | } 244 | 245 | export interface ChatCommand { 246 | sender: Player 247 | command: string 248 | } 249 | 250 | export interface ActivePlugin { 251 | config: examplePluginConfig 252 | plugin: examplePlugin 253 | api: PluginApi 254 | connection: ConnectionHandler 255 | path: string 256 | ids: { 257 | api: number 258 | plugin: number 259 | instance: number 260 | } 261 | } 262 | 263 | export interface BlockPos { 264 | x: number 265 | y: number 266 | z: number 267 | } 268 | -------------------------------------------------------------------------------- /src/berp/network/ConnectionHandler.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Packets, 3 | packet_disconnect, 4 | packet_player_list, 5 | packet_start_game, 6 | PlayerRecordsRecord, 7 | } from "../../types/packets.i" 8 | import { RakManager } from "../raknet" 9 | import { Logger } from '../../console' 10 | import { ConnectionManager } from "./ConnectionManager" 11 | import { 12 | ActivePlugin, 13 | RealmAPIWorld, 14 | } from "src/types/berp" 15 | import { BeRP } from ".." 16 | // TODO: Client/plugins can control connection/diconnection of rak 17 | 18 | 19 | export class ConnectionHandler extends RakManager { 20 | public static readonly KEEPALIVEINT = 10 21 | public readonly host: string 22 | public readonly port: number 23 | public readonly realm: RealmAPIWorld 24 | public playerQue: PlayerRecordsRecord[] = [] 25 | private _gameInfo: packet_start_game 26 | private _tickSync = 0n 27 | private _tickSyncKeepAlive: NodeJS.Timer 28 | private _connectionManager: ConnectionManager 29 | private _log: Logger 30 | private _plugins = new Map() 31 | private _berp: BeRP 32 | constructor(host: string, port: number, realm: RealmAPIWorld, cm: ConnectionManager, berp: BeRP) { 33 | super(host, port, cm.getAccount().username, realm.id) 34 | this.host = host 35 | this.port = port 36 | this.realm = realm 37 | this._connectionManager = cm 38 | this._berp = berp 39 | 40 | this._log = new Logger(`Connection Handler (${cm.getAccount().username}:${realm.id})`, 'cyanBright') 41 | 42 | this.setMaxListeners(Infinity) 43 | 44 | this.once('rak_connected', this._handleLogin.bind(this)) 45 | this.once(Packets.ServerToClientHandshake, this._handleHandshake.bind(this)) 46 | this.once(Packets.ResourcePacksInfo, async () => { 47 | await this._handleAcceptPacks() 48 | await this._cachedChunks() 49 | }) 50 | this.once(Packets.ResourcePacksStack, this._handleAcceptPacks.bind(this)) 51 | this.once(Packets.StartGame, this._handleGameStart.bind(this)) 52 | this.on(Packets.PlayerList, this._playerQue.bind(this)) 53 | this.once(Packets.Disconnect, this._handleDisconnect.bind(this)) 54 | this.once('rak_closed', this._handleDisconnect.bind(this)) 55 | 56 | this.on(Packets.TickSync, (pak) => { 57 | this._tickSync = pak.response_time 58 | }) 59 | this._log.success("Initialized") 60 | // TEMP ---- Bad Bad Bad... Dont care tho lol. BeRP v2 coming soon 61 | // The start_game packet isn't being detected by BeRP anymore, very strange... 62 | setTimeout(async () => { 63 | if(this._gameInfo) return 64 | this._registerPlugins() 65 | 66 | this.emit("rak_ready") 67 | this.removeListener('player_list', this._playerQue) 68 | await this.sendPacket(Packets.TickSync, { 69 | request_time: BigInt(Date.now()), 70 | response_time: 0n, 71 | }) 72 | this._tickSyncKeepAlive = setInterval(async () => { 73 | await this.sendPacket(Packets.TickSync, { 74 | request_time: this._tickSync, 75 | response_time: 0n, 76 | }) 77 | }, 50 * ConnectionHandler.KEEPALIVEINT) 78 | }, 5000) 79 | } 80 | public getGameInfo(): packet_start_game { return this._gameInfo } 81 | public getLogger(): Logger { return this._log } 82 | public getTick(): bigint { return this._tickSync } 83 | public getConnectionManager(): ConnectionManager { return this._connectionManager } 84 | 85 | public close(): void { 86 | super.close() 87 | this.removeAllListeners() 88 | this._connectionManager.getConnections().delete(this.realm.id) 89 | } 90 | 91 | public sendCommandFeedback(option: boolean): void { 92 | this.sendPacket('command_request', { 93 | command: `gamerule sendcommandfeedback ${option}`, 94 | interval: false, 95 | origin: { 96 | uuid: '', 97 | request_id: '', 98 | type: 'player', 99 | }, 100 | }) 101 | } 102 | 103 | private _playerQue(pak?: packet_player_list) { 104 | for (const record of pak.records.records) { 105 | if (record.username == this.getXboxProfile().extraData.displayName) continue 106 | this.playerQue.push(record) 107 | } 108 | } 109 | 110 | private async _handleDisconnect(pak?: packet_disconnect): Promise { 111 | let reason = "Rak Connection Terminated" 112 | if (pak) { 113 | reason = pak.message 114 | } 115 | 116 | await this._berp.getPluginManager().killPlugins(this) 117 | clearInterval(this._tickSyncKeepAlive) 118 | this.close() 119 | this._log.warn(`Terminating connection handler with connection "${this.host}:${this.port}"`) 120 | 121 | this._log.warn("Disconnection on", `${this.host}:${this.port}`, `"${reason}"`) 122 | } 123 | private async _handleLogin(): Promise { 124 | await this.sendPacket(Packets.Login, this.createLoginPayload()) 125 | } 126 | private async _handleHandshake(): Promise { 127 | setTimeout(async () => { 128 | await this.sendPacket(Packets.ClientToServerHandshake, {}) 129 | },0) 130 | } 131 | private async _handleAcceptPacks(): Promise { 132 | await this.sendPacket(Packets.ResourcePackClientResponse, { 133 | response_status: 'completed', 134 | resourcepackids: [], 135 | }) 136 | } 137 | private async _cachedChunks(): Promise { 138 | await this.sendPacket(Packets.ClientCacheStatus, { 139 | enabled: false, 140 | }) 141 | await this.sendPacket(Packets.RequestChunkRadius, { 142 | chunk_radius: 1, 143 | }) 144 | } 145 | private async _handleGameStart(pak: packet_start_game): Promise { 146 | console.log('game started... If you see this, message PMK744.') 147 | this._gameInfo = pak 148 | await this.sendPacket(Packets.SetLocalPlayerAsInitialized, { 149 | runtime_entity_id: pak.runtime_entity_id, 150 | }) 151 | this.emit("rak_ready") 152 | this._registerPlugins() 153 | this.removeListener('player_list', this._playerQue) 154 | await this.sendPacket(Packets.TickSync, { 155 | request_time: BigInt(Date.now()), 156 | response_time: 0n, 157 | }) 158 | this._tickSyncKeepAlive = setInterval(async () => { 159 | await this.sendPacket(Packets.TickSync, { 160 | request_time: this._tickSync, 161 | response_time: 0n, 162 | }) 163 | }, 50 * ConnectionHandler.KEEPALIVEINT) 164 | } 165 | private async _registerPlugins(): Promise { 166 | const plugins = await this._berp.getPluginManager().registerPlugins(this) 167 | for (const plugin of plugins) { 168 | this._plugins.set(plugin.config.name, plugin) 169 | } 170 | } 171 | public getPlugins(): Map { return this._plugins } 172 | } 173 | -------------------------------------------------------------------------------- /src/protodef.index.d.ts: -------------------------------------------------------------------------------- 1 | declare class ProtodefValidator { 2 | constructor(typesSchemas: unknown) 3 | public createAjvInstance(typesSchemas): void 4 | public addDefaultTypes(): void 5 | public addTypes(schemas): void 6 | public typeToSchemaName(name: string): string 7 | public addType(name: string, schema: unknown): void 8 | public validateType(type: unknown): void 9 | public validateTypeGoingInside(type: unknown): void 10 | public validateProtocol(protocol: unknown): void 11 | } 12 | 13 | declare class ProtodefBasecompiler { 14 | public primitiveTypes: unknown 15 | public native: unknown 16 | public context: unknown 17 | public types: unknown 18 | public scopeStack: unknown[] 19 | public parameterizableTypes: unknown 20 | constructor() 21 | public addNativeType (type: unknown, fn: unknown): void 22 | public addContextType (type: unknown, fn: unknown): void 23 | public addParametrizableType(type: unknown, maker: unknown): void 24 | public addTypes (types: unknown): void 25 | public addTypesToCompile (types: unknown): void 26 | public addProtocol (protocolData: unknown, path: unknown): void 27 | public indent(code: string, indent: string): string 28 | public getField (name: string): unknown 29 | public generate(): string 30 | public compile (code: unknown): unknown 31 | } 32 | 33 | declare class ProtodefReadCompiler extends ProtodefBasecompiler { 34 | constructor() 35 | public compileType (type: unknown): unknown 36 | public wrapCode (code: unknown, args: unknown[]): unknown 37 | public callType (type: unknown, offsetExpr: string, args: unknown[]): unknown 38 | } 39 | 40 | declare class ProtodefWriteCompiler extends ProtodefBasecompiler { 41 | constructor() 42 | public compileType (type: unknown): unknown 43 | public wrapCode (code: unknown, args: unknown[]): unknown 44 | public callType (type: unknown, offsetExpr: string, args: unknown[]): unknown 45 | } 46 | 47 | declare class ProtodefSizeOfCompiler extends ProtodefBasecompiler { 48 | constructor() 49 | public addNativeType (type: unknown, fn: unknown): void 50 | public compileType (type: unknown): unknown 51 | public wrapCode (code: unknown, args: unknown[]): unknown 52 | public callType (type: unknown, offsetExpr: string, args: unknown[]): unknown 53 | } 54 | 55 | declare class ProtodefCompiler { 56 | public readCompiler: ProtodefReadCompiler 57 | public writeCompiler: ProtodefWriteCompiler 58 | public sizeOfCompiler: ProtodefSizeOfCompiler 59 | constructor() 60 | public addTypes (types: unknown): void 61 | public addTypesToCompile (types: unknown): void 62 | public addProtocol (protocolData: unknown, path: unknown): void 63 | public addVariable (key: unknown, val: unknown): void 64 | public compileProtoDefSync (options?: unknown): unknown 65 | } 66 | 67 | declare class CompiledProtodef { 68 | public sizeOfCtx: unknown 69 | public writeCtx: unknown 70 | public readCtx: unknown 71 | constructor (sizeOfCtx: unknown, writeCtx: unknown, readCtx: unknown) 72 | public read (buffer: unknown, cursor: unknown, type: unknown): unknown 73 | public write (value: unknown, buffer: unknown, cursor: unknown, type: unknown): unknown 74 | public setVariable (key: unknown, val: unknown): void 75 | public sizeOf (value: unknown, type: unknown): unknown 76 | public createPacketBuffer (type: unknown, packet: unknown): unknown 77 | public parsePacketBuffer (type: unknown, buffer: unknown, offset: number): unknown 78 | } 79 | 80 | declare class ProtodefExtendableError extends Error { 81 | constructor(message: unknown) 82 | } 83 | 84 | declare class ProtodefPartialError extends Error { 85 | public partialReadError: boolean 86 | constructor(message: unknown) 87 | } 88 | 89 | declare module "protodef" { 90 | import { Transform } from 'readable-stream' 91 | class ProtoDef { 92 | public types: unknown 93 | public validator: ProtodefValidator 94 | public addDefaultTypes(): void 95 | public addProtocol(protocolData: unknown, path: unknown): void 96 | public addType(name: unknown, functions: unknown, validate: boolean): void 97 | public addTypes(types: unknown): void 98 | public setVariable(key: unknown, value: unknown): void 99 | public read(buffer: Buffer, cursor: unknown, _fieldInfo: unknown, rootNodes: unknown): unknown 100 | public write (value: unknown, buffer: Buffer, offset: number, _fieldInfo:unknown, rootNode: unknown): unknown 101 | public sizeOf (value: unknown, _fieldInfo: unknown, rootNode:unknown): unknown 102 | public createPacketBuffer (type: unknown, packet: unknown): unknown 103 | public parsePacketBuffer (type: unknown, buffer: Buffer, offset: number): unknown 104 | } 105 | class Serializer extends Transform { 106 | public proto: unknown 107 | public mainType: unknown 108 | public queue: unknown 109 | constructor(proto: unknown, mainType: unknown) 110 | public createPacketBuffer (packet: unknown): unknown 111 | public _transform (chunk: unknown, enc: unknown, cb: unknown): unknown 112 | } 113 | class Parser extends Transform { 114 | public proto: unknown 115 | public mainType: unknown 116 | public queue: unknown 117 | constructor(proto: unknown, mainType: unknown) 118 | public parsePacketBuffer (packet: unknown): unknown 119 | public _transform (chunk: unknown, enc: unknown, cb: unknown): unknown 120 | } 121 | class FullPacketParser { 122 | public proto: unknown 123 | public mainType: unknown 124 | public noErrorLogging: unknown 125 | constructor(proto: unknown, mainType: unknown) 126 | public parsePacketBuffer (packet: unknown): unknown 127 | public _transform (chunk: unknown, enc: unknown, cb: unknown): unknown 128 | } 129 | const Compiler: { 130 | ReadCompiler: typeof ProtodefReadCompiler 131 | WriteCompiler: typeof ProtodefWriteCompiler 132 | SizeOfCompiler: typeof ProtodefSizeOfCompiler 133 | ProtoDefCompiler: typeof ProtodefCompiler 134 | CompiledProtodef: typeof CompiledProtodef 135 | } 136 | const types: { 137 | varint: [ 138 | (buf: Buffer, offset: number) => { value: number, size: number }, 139 | (length: number, buffer: Buffer, num: number) => void, 140 | (chunk: number) => number 141 | ] 142 | } 143 | const utils: { 144 | getField (countField: unknown, context: unknown): unknown 145 | getFieldInfo (fieldInfo: unknown): unknown 146 | getCount (buffer: unknown, offset: unknown, options: unknown, rootNode: unknown): unknown 147 | sendCount (len: unknown, buffer: unknown, offset: unknown, options: unknown, rootNode: unknown): unknown 148 | calcCount (len: unknown, options: unknown, rootNode: unknown): unknown 149 | addErrorField (e: unknown, field: unknown): void 150 | tryCatch (tryfn: unknown, catchfn: unknown): void 151 | PartialReadError: ProtodefPartialError 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/player/Player.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionHandler } from 'src/berp/network' 2 | import { BeRP } from 'src/berp' 3 | import { PluginApi } from '../pluginApi' 4 | import { 5 | BlockPos, 6 | PlayerOptions, 7 | } from 'src/types/berp' 8 | import { Skin } from 'src/types/packetTypes.i' 9 | import { packet_command_output } from 'src/types/packets.i' 10 | import { 11 | Inventory, 12 | InventoryRequest, 13 | } from 'src/types/pluginApi.i' 14 | 15 | export class Player { 16 | private _name: string 17 | private _nameTag: string 18 | private _realmID: number 19 | private _uuid: string 20 | private _xuid: string 21 | private _entityID: bigint 22 | private _device: number 23 | private _skinData: Skin 24 | private _berp: BeRP 25 | private _connection: ConnectionHandler 26 | private _pluginApi: PluginApi 27 | constructor(options: PlayerOptions, berp: BeRP, connection: ConnectionHandler, pluginApi: PluginApi) { 28 | this._name = options.name 29 | this._nameTag = options.name 30 | this._realmID = connection.realm.id 31 | this._uuid = options.uuid 32 | this._xuid = options.xuid 33 | this._entityID = options.entityID 34 | this._device = options.device 35 | this._skinData = options.skinData 36 | this._berp = berp 37 | this._connection = connection 38 | this._pluginApi = pluginApi 39 | this._pluginApi.getPlayerManager().addPlayer(this) 40 | } 41 | public getName(): string { return this._name } 42 | public getNameTag(): string { return this._nameTag } 43 | public getRealmID(): number { return this._realmID } 44 | public getUUID(): string { return this._uuid } 45 | public getXuid(): string { return this._xuid } 46 | public getEntityID(): bigint { return this._entityID } 47 | public getDevice(): string { 48 | switch(this._device) { 49 | case 1: 50 | return 'Android' 51 | case 2: 52 | return 'iOS' 53 | case 3: 54 | return 'iOS' 55 | case 4: 56 | return 'Kindle Fire' 57 | case 7: 58 | return 'Windows' 59 | case 11: 60 | return 'PlayStation' 61 | case 12: 62 | return 'Switch' 63 | case 13: 64 | return 'Xbox' 65 | default: 66 | return `Unknown ID: ${this._device}` 67 | } 68 | } 69 | public getSkinData(): Skin { return this._skinData } 70 | public getExecutionName(): string { 71 | if (this._name != this._nameTag) return this._nameTag 72 | 73 | return this._name 74 | } 75 | public getConnection(): ConnectionHandler { return this._connection } 76 | public setNameTag(nameTag: string): void { 77 | if (!this._pluginApi.getSocketManager().enabled) return this._pluginApi.getLogger().error("setNameTag() can't be used because there is no Socket Connection.") 78 | this._pluginApi.getPlayerManager().updatePlayerNameTag(this, nameTag) 79 | this._nameTag = nameTag 80 | } 81 | public setNameTagBackDoor(nameTag: string): void { 82 | this._pluginApi.getPlayerManager().updatePlayerNameTag(this, nameTag, false) 83 | this._nameTag = nameTag 84 | } 85 | public sendMessage(message: string): void { 86 | this._pluginApi.getCommandManager().executeCommand(`tellraw "${this.getExecutionName()}" {"rawtext":[{"text":"${message}"}]}`) 87 | } 88 | public sendTitle(message: string, slot: 'actionbar' | 'title' | 'subtitle'): void { 89 | this._pluginApi.getCommandManager().executeCommand(`titleraw "${this.getExecutionName()}" ${slot} {"rawtext":[{"text":"${message}"}]}`) 90 | } 91 | public executeCommand(command: string, callback?: (data: packet_command_output) => void): void { 92 | if (callback) { 93 | this._pluginApi.getCommandManager().executeCommand(`execute "${this.getExecutionName()}" ~ ~ ~ ${command}`, (data) => { 94 | callback(data) 95 | }) 96 | } else { 97 | this._pluginApi.getCommandManager().executeCommand(`execute "${this.getExecutionName()}" ~ ~ ~ ${command}`) 98 | } 99 | } 100 | public async getTags(): Promise { 101 | return new Promise((r) => { 102 | this._pluginApi.getCommandManager().executeCommand(`tag "${this.getExecutionName()}" list`, (res) => { 103 | if (!res.output[0].paramaters[1]) return r([]) 104 | const filter = [res.output[0].paramaters[0], res.output[0].paramaters[1]] 105 | const tags = res.output[0].paramaters.filter(x => !filter.includes(x)).toString() 106 | .replace(/§\S/g, "") 107 | .split(', ') 108 | 109 | return r(tags) 110 | }) 111 | }) 112 | } 113 | public async hasTag(tag: string): Promise { 114 | if (!(await this.getTags()).includes(tag)) return false 115 | 116 | return true 117 | } 118 | public addTag(tag: string): void { 119 | this._pluginApi.getCommandManager().executeCommand(`tag "${this.getExecutionName()}" add "${tag}"`) 120 | } 121 | public removeTag(tag: string): void { 122 | this._pluginApi.getCommandManager().executeCommand(`tag "${this.getExecutionName()}" remove "${tag}"`) 123 | } 124 | public async getScore(objective: string): Promise { 125 | return new Promise((r) => { 126 | this._pluginApi.getCommandManager().executeCommand(`scoreboard players test "${this.getExecutionName()}" ${objective} * *`, (res) => { 127 | if (res.output[0].paramaters[0] == this._name) return r(0) 128 | 129 | return r(parseInt(res.output[0].paramaters[0])) 130 | }) 131 | }) 132 | } 133 | public updateScore(operation: 'add' | 'remove' | 'set', objective: string, value: number): void { 134 | this._pluginApi.getCommandManager().executeCommand(`scoreboard players ${operation} "${this.getExecutionName()}" ${objective} ${value}`) 135 | } 136 | public kick(reason: string): void { 137 | this._pluginApi.getCommandManager().executeCommand(`kick "${this.getExecutionName()}" ${reason}`) 138 | } 139 | public async getItemCount(item: string): Promise { 140 | return new Promise((r) => { 141 | this._pluginApi.getCommandManager().executeCommand(`clear "${this.getExecutionName()}" ${item} 0 0`, (res) => { 142 | let count = res.output[0].paramaters[1] 143 | if (count == undefined) count = '0' 144 | 145 | return r(parseInt(count)) 146 | }) 147 | }) 148 | } 149 | public async getLocation(): Promise { 150 | if (!this._pluginApi.getSocketManager().enabled) return this._pluginApi.getLogger().error("getLocation() can't be used because there is no Socket Connection.") 151 | 152 | return new Promise((res) => { 153 | this._pluginApi.getSocketManager().sendMessage({ 154 | berp: { 155 | event: "PlayerRequest", 156 | player: this._name, 157 | requestId: this._pluginApi.getSocketManager().newUUID(), 158 | }, 159 | }, (packet) => { 160 | if (!packet.player) return res({ 161 | x: 0, 162 | y: 0, 163 | z: 0, 164 | }) 165 | 166 | return res(packet.player.location) 167 | }) 168 | }) 169 | } 170 | public async getInventory(): Promise { 171 | if (!this._pluginApi.getSocketManager().enabled) return this._pluginApi.getLogger().error("getInventory() can't be used because there is no Socket Connection.") 172 | 173 | return new Promise((res) => { 174 | this._pluginApi.getSocketManager() 175 | .sendMessage({ 176 | berp: { 177 | event: "InventoryRequest", 178 | player: this.getName(), 179 | requestId: this._pluginApi.getSocketManager() 180 | .newUUID(), 181 | }, 182 | }, (packet: InventoryRequest) => { 183 | return res(packet.data) 184 | }) 185 | }) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /data/latest/LEGAL.md: -------------------------------------------------------------------------------- 1 | ## [minecraft-data](https://github.com/PrismarineJS/minecraft-data) 2 | 3 | ``` 4 | MIT 5 | 6 | Some of the data was extracted manually or automatically from wiki.vg and minecraft.gamepedia.com. 7 | If required by one of the sources the license might change to something more appropriate. 8 | ``` 9 | 10 | ## [dragonfly](https://github.com/df-mc/dragonfly) 11 | 12 | ``` 13 | MIT License 14 | 15 | Copyright (c) 2019 Dragonfly Tech 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | ``` 35 | 36 | ## [PocketMine-MP](https://github.com/pmmp/PocketMine-MP) 37 | 38 | ``` 39 | GNU LESSER GENERAL PUBLIC LICENSE 40 | Version 3, 29 June 2007 41 | 42 | Copyright (C) 2007 Free Software Foundation, Inc. 43 | Everyone is permitted to copy and distribute verbatim copies 44 | of this license document, but changing it is not allowed. 45 | 46 | 47 | This version of the GNU Lesser General Public License incorporates 48 | the terms and conditions of version 3 of the GNU General Public 49 | License, supplemented by the additional permissions listed below. 50 | 51 | 0. Additional Definitions. 52 | 53 | As used herein, "this License" refers to version 3 of the GNU Lesser 54 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 55 | General Public License. 56 | 57 | "The Library" refers to a covered work governed by this License, 58 | other than an Application or a Combined Work as defined below. 59 | 60 | An "Application" is any work that makes use of an interface provided 61 | by the Library, but which is not otherwise based on the Library. 62 | Defining a subclass of a class defined by the Library is deemed a mode 63 | of using an interface provided by the Library. 64 | 65 | A "Combined Work" is a work produced by combining or linking an 66 | Application with the Library. The particular version of the Library 67 | with which the Combined Work was made is also called the "Linked 68 | Version". 69 | 70 | The "Minimal Corresponding Source" for a Combined Work means the 71 | Corresponding Source for the Combined Work, excluding any source code 72 | for portions of the Combined Work that, considered in isolation, are 73 | based on the Application, and not on the Linked Version. 74 | 75 | The "Corresponding Application Code" for a Combined Work means the 76 | object code and/or source code for the Application, including any data 77 | and utility programs needed for reproducing the Combined Work from the 78 | Application, but excluding the System Libraries of the Combined Work. 79 | 80 | 1. Exception to Section 3 of the GNU GPL. 81 | 82 | You may convey a covered work under sections 3 and 4 of this License 83 | without being bound by section 3 of the GNU GPL. 84 | 85 | 2. Conveying Modified Versions. 86 | 87 | If you modify a copy of the Library, and, in your modifications, a 88 | facility refers to a function or data to be supplied by an Application 89 | that uses the facility (other than as an argument passed when the 90 | facility is invoked), then you may convey a copy of the modified 91 | version: 92 | 93 | a) under this License, provided that you make a good faith effort to 94 | ensure that, in the event an Application does not supply the 95 | function or data, the facility still operates, and performs 96 | whatever part of its purpose remains meaningful, or 97 | 98 | b) under the GNU GPL, with none of the additional permissions of 99 | this License applicable to that copy. 100 | 101 | 3. Object Code Incorporating Material from Library Header Files. 102 | 103 | The object code form of an Application may incorporate material from 104 | a header file that is part of the Library. You may convey such object 105 | code under terms of your choice, provided that, if the incorporated 106 | material is not limited to numerical parameters, data structure 107 | layouts and accessors, or small macros, inline functions and templates 108 | (ten or fewer lines in length), you do both of the following: 109 | 110 | a) Give prominent notice with each copy of the object code that the 111 | Library is used in it and that the Library and its use are 112 | covered by this License. 113 | 114 | b) Accompany the object code with a copy of the GNU GPL and this license 115 | document. 116 | 117 | 4. Combined Works. 118 | 119 | You may convey a Combined Work under terms of your choice that, 120 | taken together, effectively do not restrict modification of the 121 | portions of the Library contained in the Combined Work and reverse 122 | engineering for debugging such modifications, if you also do each of 123 | the following: 124 | 125 | a) Give prominent notice with each copy of the Combined Work that 126 | the Library is used in it and that the Library and its use are 127 | covered by this License. 128 | 129 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 130 | document. 131 | 132 | c) For a Combined Work that displays copyright notices during 133 | execution, include the copyright notice for the Library among 134 | these notices, as well as a reference directing the user to the 135 | copies of the GNU GPL and this license document. 136 | 137 | d) Do one of the following: 138 | 139 | 0) Convey the Minimal Corresponding Source under the terms of this 140 | License, and the Corresponding Application Code in a form 141 | suitable for, and under terms that permit, the user to 142 | recombine or relink the Application with a modified version of 143 | the Linked Version to produce a modified Combined Work, in the 144 | manner specified by section 6 of the GNU GPL for conveying 145 | Corresponding Source. 146 | 147 | 1) Use a suitable shared library mechanism for linking with the 148 | Library. A suitable mechanism is one that (a) uses at run time 149 | a copy of the Library already present on the user's computer 150 | system, and (b) will operate properly with a modified version 151 | of the Library that is interface-compatible with the Linked 152 | Version. 153 | 154 | e) Provide Installation Information, but only if you would otherwise 155 | be required to provide such information under section 6 of the 156 | GNU GPL, and only to the extent that such information is 157 | necessary to install and execute a modified version of the 158 | Combined Work produced by recombining or relinking the 159 | Application with a modified version of the Linked Version. (If 160 | you use option 4d0, the Installation Information must accompany 161 | the Minimal Corresponding Source and Corresponding Application 162 | Code. If you use option 4d1, you must provide the Installation 163 | Information in the manner specified by section 6 of the GNU GPL 164 | for conveying Corresponding Source.) 165 | 166 | 5. Combined Libraries. 167 | 168 | You may place library facilities that are a work based on the 169 | Library side by side in a single library together with other library 170 | facilities that are not Applications and are not covered by this 171 | License, and convey such a combined library under terms of your 172 | choice, if you do both of the following: 173 | 174 | a) Accompany the combined library with a copy of the same work based 175 | on the Library, uncombined with any other library facilities, 176 | conveyed under the terms of this License. 177 | 178 | b) Give prominent notice with the combined library that part of it 179 | is a work based on the Library, and explaining where to find the 180 | accompanying uncombined form of the same work. 181 | 182 | 6. Revised Versions of the GNU Lesser General Public License. 183 | 184 | The Free Software Foundation may publish revised and/or new versions 185 | of the GNU Lesser General Public License from time to time. Such new 186 | versions will be similar in spirit to the present version, but may 187 | differ in detail to address new problems or concerns. 188 | 189 | Each version is given a distinguishing version number. If the 190 | Library as you received it specifies that a certain numbered version 191 | of the GNU Lesser General Public License "or any later version" 192 | applies to it, you have the option of following the terms and 193 | conditions either of that published version or of any later version 194 | published by the Free Software Foundation. If the Library as you 195 | received it does not specify a version number of the GNU Lesser 196 | General Public License, you may choose any version of the GNU Lesser 197 | General Public License ever published by the Free Software Foundation. 198 | 199 | If the Library as you received it specifies that a proxy can decide 200 | whether future versions of the GNU Lesser General Public License shall 201 | apply, that proxy's public statement of acceptance of any version is 202 | permanent authorization for you to choose that version for the 203 | Library. 204 | ``` 205 | -------------------------------------------------------------------------------- /src/berp/plugin/pluginapi/PluginApi.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '../../../console' 2 | import { 3 | ActivePlugin, 4 | examplePlugin, 5 | examplePluginConfig, 6 | RealmAPIJoinInfo, 7 | RealmAPIWorld, 8 | RealmAPIWorldsRes, 9 | } from 'src/types/berp' 10 | import { BeRP } from '../../' 11 | import { ConnectionHandler } from 'src/berp/network' 12 | import { CommandManager } from './command/CommandManager' 13 | import { PlayerManager } from './player/PlayerManager' 14 | import { EntityManager } from './entity/EntityManager' 15 | import { WorldManager } from './world/WorldManager' 16 | import { RealmManager } from './realm/RealmManager' 17 | import { SocketManager } from './socket/SocketManager' 18 | import { EventManager } from './events/EventManager' 19 | import fs from 'fs' 20 | import path from 'path' 21 | import { AccountInfo } from '@azure/msal-common' 22 | import * as C from '../../../Constants' 23 | import { createXBLToken } from '../../../berp/utils' 24 | 25 | export class PluginApi { 26 | private _berp: BeRP 27 | private _logger: Logger 28 | private _config: examplePluginConfig 29 | private _connection: ConnectionHandler 30 | private _apiId: number 31 | private _pluginId: number 32 | private _instanceId: number 33 | private _commandManager: CommandManager 34 | private _playerManager: PlayerManager 35 | private _entityManager: EntityManager 36 | private _worldManager: WorldManager 37 | private _realmManager: RealmManager 38 | private _socketManager: SocketManager 39 | private _eventManager: EventManager 40 | private _temp: boolean 41 | private _hasConnected = false 42 | private _interval 43 | public path: string 44 | constructor (berp: BeRP, config: examplePluginConfig, path: string, connection: ConnectionHandler, apis: { apiId: number, pluginId: number, instanceId: number }, temp = false) { 45 | this._berp = berp 46 | this._logger = new Logger(`${config.displayName} ${connection.realm.id || "(Init)"}`, config.color) 47 | this._config = config 48 | this._connection = connection 49 | this._apiId = apis.apiId 50 | this._pluginId = apis.pluginId 51 | this._instanceId = apis.instanceId 52 | this._temp = temp 53 | this.path = path 54 | if (this._temp) return 55 | this._playerManager = new PlayerManager(this._berp, this._connection, this) 56 | this._entityManager = new EntityManager(this._berp, this._connection, this) 57 | this._socketManager = new SocketManager(this._berp, this._connection, this) 58 | this._eventManager = new EventManager(this._berp, this._connection, this) 59 | this._commandManager = new CommandManager(this._berp, this._connection, this) 60 | this._worldManager = new WorldManager(this._berp, this._connection, this) 61 | this._realmManager = new RealmManager(this._berp, this._connection, this) 62 | } 63 | public async onEnabled(): Promise { 64 | if (this._temp) return 65 | await this._commandManager.onEnabled() 66 | await this._playerManager.onEnabled() 67 | await this._entityManager.onEnabled() 68 | await this._worldManager.onEnabled() 69 | await this._realmManager.onEnabled() 70 | await this._socketManager.onEnabled() 71 | await this._eventManager.onEnabled() 72 | 73 | return 74 | } 75 | public async onDisabled(): Promise { 76 | clearInterval(this._interval) 77 | if (this._temp) return 78 | await this._commandManager.onDisabled() 79 | await this._playerManager.onDisabled() 80 | await this._entityManager.onDisabled() 81 | await this._worldManager.onDisabled() 82 | await this._realmManager.onDisabled() 83 | await this._socketManager.onDisabled() 84 | await this._eventManager.onDisabled() 85 | 86 | return 87 | } 88 | public getLogger(): Logger { return this._logger } 89 | public getConnection(): ConnectionHandler { return this._connection } 90 | public getConfig(): examplePluginConfig { return this._config } 91 | public getApiId(): number { return this._apiId } 92 | public getPluginId(): number { return this._pluginId } 93 | public getInstanceId(): number { return this._instanceId } 94 | public getCommandManager(): CommandManager { return this._commandManager } 95 | public getPlayerManager(): PlayerManager { return this._playerManager } 96 | public getEntityManager(): EntityManager { return this._entityManager } 97 | public getWorldManager(): WorldManager { return this._worldManager } 98 | public getRealmManager(): RealmManager { return this._realmManager } 99 | public getSocketManager(): SocketManager { return this._socketManager } 100 | public getEventManager(): EventManager { return this._eventManager } 101 | public getPlugins(): Map { 102 | const plugins = new Map() 103 | for (const [, entry] of this._berp.getPluginManager().getActivePlugins()) { 104 | if (this._connection !== entry.connection) continue 105 | plugins.set(entry.config.name, entry) 106 | } 107 | 108 | return plugins 109 | } 110 | public getPluginByInstanceId(name: string, id: number): Promise { 111 | return new Promise((res) => { 112 | for (const [, plugin] of this._berp.getPluginManager().getActivePlugins()) { 113 | if (plugin.config.name.toLocaleLowerCase() != name.toLocaleLowerCase() && plugin.ids.instance != id) continue 114 | 115 | return res(plugin) 116 | } 117 | }) 118 | } 119 | public createInterface(options: {name: string, interface: string}): void { 120 | setTimeout(() => { 121 | for (const [, entry] of this.getPlugins()) { 122 | fs.writeFileSync(path.resolve(entry.path, 'src', '@interface', `${options.name}.i.ts`), options.interface) 123 | } 124 | }, 1000) 125 | } 126 | public async autoConnect(accountEmail: string, realmId: number, bypass = false): Promise { 127 | return new Promise(async (resX) => { 128 | if (!this._temp && !bypass) { 129 | this._logger.error("AutoConnect is only allowed in the onLoaded() method!") 130 | 131 | return resX(false) 132 | } 133 | const foundAccounts = new Map() 134 | const accounts = await this._berp 135 | .getAuthProvider() 136 | .getCache() 137 | .getAllAccounts() 138 | for (const account of accounts) { 139 | foundAccounts.set(account.username, account) 140 | } 141 | if (!foundAccounts.has(accountEmail)) { 142 | this._logger.error(`No account found with the email "${accountEmail}"`) 143 | 144 | return resX(false) 145 | } 146 | const account = foundAccounts.get(accountEmail) 147 | const authRes = await this._berp.getAuthProvider().aquireTokenFromCache({ 148 | scopes: C.Scopes, 149 | account, 150 | }) 151 | const xsts = await this._berp.getAuthProvider().ezXSTSForRealmAPI(authRes) 152 | 153 | let net = this._berp.getNetworkManager().getAccounts() 154 | .get(account.username) 155 | if (!net) { 156 | net = this._berp.getNetworkManager().create(account) 157 | } 158 | const foundRealms = new Map() 159 | const req = new this._berp.Request({ 160 | method: "GET", 161 | url: C.Endpoints.RealmAPI.GET.Realms, 162 | headers: C.RealmAPIHeaders(createXBLToken(xsts)), 163 | }, { 164 | requestTimeout: 50000, 165 | attemptTimeout: 300, 166 | attempts: 20, 167 | }) 168 | req.onFufilled = async (res: RealmAPIWorldsRes) => { 169 | if (!res.servers || !res.servers.length) return this._logger.error(`No realms could be found under the account "${account.username}"`) 170 | for await (const server of res.servers) { 171 | foundRealms.set(server.id, server) 172 | } 173 | if (!foundRealms.has(realmId)) return this._logger.error(`No realm with the Id "${realmId}" was found.`) 174 | const realm = foundRealms.get(realmId) 175 | const req = new this._berp.Request({ 176 | method: "GET", 177 | url: C.Endpoints.RealmAPI.GET.RealmJoinInfo(realm.id), 178 | headers: C.RealmAPIHeaders(createXBLToken(xsts)), 179 | }, { 180 | requestTimeout: 50000, 181 | attemptTimeout: 300, 182 | attempts: 100, 183 | }) 184 | req.onFufilled = (res: RealmAPIJoinInfo) => { 185 | const split = res.address.split(":") 186 | const ip = split[0] 187 | const port = parseInt(split[1]) 188 | net.newConnection(ip, port, realm) 189 | this._hasConnected = true 190 | 191 | return resX(true) 192 | } 193 | req.onFailed = () => { 194 | this._logger.error(`Failed to get join info for realm "${realm.name}"`) 195 | 196 | return resX(false) 197 | } 198 | this._berp.getSequentialBucket().addRequest(req) 199 | } 200 | req.onFailed = () => { 201 | this._logger.error(`Failed to select account for realm connection...`) 202 | 203 | return resX(false) 204 | } 205 | this._berp.getSequentialBucket().addRequest(req) 206 | }) 207 | } 208 | public autoReconnect(accountEmail: string, realmId: number): void { 209 | let tryConnection = true 210 | if (!this._temp) return this._logger.error("AutoReconnect is only allowed in the onLoaded() method!") 211 | this._interval = setInterval(async () => { 212 | if (!this._hasConnected || !tryConnection) return 213 | const accounts = this._berp.getNetworkManager().getAccounts() 214 | if (!accounts.has(accountEmail)) return 215 | const account = accounts.get(accountEmail) 216 | if (account.getConnections().has(realmId)) return 217 | this._logger.success(`AutoReconnect attempting to connect to realm Id "${realmId}" using the email "${accountEmail}"`) 218 | tryConnection = false 219 | await this.autoConnect(accountEmail, realmId) 220 | tryConnection = true 221 | }, 10000) 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/berp/raknet/Manager.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { 3 | KeyPairKeyObjectResult, 4 | generateKeyPairSync, 5 | KeyObject, 6 | createPublicKey, 7 | diffieHellman, 8 | createHash, 9 | Hash, 10 | } from 'crypto' 11 | import { 12 | createXBLToken, 13 | DataProvider, 14 | nextUUID, 15 | } from '../utils' 16 | import { 17 | AuthHandlerXSTSResponse, 18 | XboxProfile, 19 | } from '../../types/berp' 20 | import { 21 | ClientBoundPackets, 22 | packet_login, 23 | ServerBoundPackets, 24 | } from '../../types/packets.i' 25 | import { PacketHandler } from './PacketHandler' 26 | import { Raknet } from './UDP' 27 | import { EventEmitter } from 'events' 28 | import { Logger } from '../../console' 29 | import Axios, { Method } from 'axios' 30 | import * as C from '../../Constants' 31 | import JWT from 'jsonwebtoken' 32 | 33 | export interface RakManager { 34 | on(event: K, listener: (...args: ClientBoundPackets[K]) => void): this 35 | on( 36 | event: Exclude, 37 | listener: (...args: any[]) => void, 38 | ): this 39 | once(event: K, listener: (...args: ClientBoundPackets[K]) => void): this 40 | once( 41 | event: Exclude, 42 | listener: (...args: any[]) => void, 43 | ): this 44 | emit(event: K, ...args: ClientBoundPackets[K]): boolean 45 | emit( 46 | event: Exclude, 47 | ...args: any[] 48 | ): boolean 49 | } 50 | export class RakManager extends EventEmitter { 51 | public readonly host: string 52 | public readonly port: number 53 | public readonly id: number 54 | private _logger: Logger 55 | private edchKeyPair: KeyPairKeyObjectResult 56 | private publicKeyDER: string | Buffer 57 | private privateKeyPEM: string | Buffer 58 | private clientIdentityChain: string 59 | private clientUserChain: string 60 | private mcAuthChains: string[] = [] 61 | private _xboxProfile: XboxProfile 62 | private packetHandler: PacketHandler 63 | private _raknet: Raknet 64 | private encryptionStarted = false 65 | private serverBoundEncryptionToken: string 66 | private encryptionPubKeyDer: KeyObject 67 | private encryptionSharedSecret: Buffer 68 | private encryptionSalt: Buffer 69 | private encryptionSecretHash: Hash 70 | private encryptionSecretKeyBytes: Buffer 71 | private encryptionIV: Buffer 72 | public readonly version = C.CUR_VERSION 73 | public readonly X509: string 74 | public readonly SALT = "🧂" 75 | public readonly CURVE = "secp384r1" 76 | public readonly ALGORITHM = "ES384" 77 | public readonly PUBLIC_KEY_ONLINE = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V" 78 | private connected = false 79 | constructor(host: string, port: number, username: string, id: number) { 80 | super() 81 | 82 | this.host = host 83 | this.port = port 84 | this.id = id 85 | this._logger = new Logger(`Raknet (${username}:${id})`, 'yellow') 86 | 87 | this.packetHandler = new PacketHandler() 88 | 89 | this.edchKeyPair = generateKeyPairSync('ec', { namedCurve: this.CURVE }) 90 | this.publicKeyDER = this.edchKeyPair.publicKey.export({ 91 | format: 'der', 92 | type: 'spki', 93 | }) 94 | this.privateKeyPEM = this.edchKeyPair.privateKey.export({ 95 | format: 'pem', 96 | type: 'sec1', 97 | }) 98 | this.X509 = this.publicKeyDER.toString('base64') 99 | 100 | this._raknet = new Raknet(host, port, 10) 101 | this._handlePackets() 102 | } 103 | public getRakLogger(): Logger { return this._logger } 104 | public getXboxProfile(): XboxProfile { return this._xboxProfile } 105 | 106 | private _handlePackets(): void { 107 | this._raknet.on('connected', () => { 108 | this.emit('rak_connected') 109 | }) 110 | this._raknet.on('closed', () => { 111 | this.emit('rak_closed') 112 | }) 113 | this._raknet.on('pong', () => { 114 | this.emit('rak_pong') 115 | }) 116 | this._raknet.on('raw', async (packet) => { 117 | // console.log(packet) 118 | try { 119 | for (const pak of await this.packetHandler.readPacket(packet)) { 120 | // console.log(pak.name) 121 | this.emit("all", { 122 | name: pak.name, 123 | params: pak.params, 124 | }) 125 | this.emit(pak.name, pak.params as any) 126 | } 127 | } catch (err) { 128 | const error = "Failed to read imbound packet: " + err 129 | this._logger.error(error) 130 | } 131 | }) 132 | } 133 | 134 | public async connect(): Promise { 135 | if (!this.connected) { 136 | this.connected = true 137 | if (!this.mcAuthChains.length) throw new Error("Auth Mc First") 138 | 139 | this.updateXboxUserData() 140 | this._generateClientIdentityChain() 141 | 142 | this.on('server_to_client_handshake', (data) => { 143 | this.serverBoundEncryptionToken = data.token 144 | this._startServerboundEncryption() 145 | }) 146 | 147 | this._raknet.connect() 148 | } 149 | } 150 | 151 | public close(): void { 152 | this.emit('rak_closed') 153 | this._raknet.killConnection() 154 | this.removeAllListeners() 155 | } 156 | 157 | private _startServerboundEncryption(): void { 158 | const [header, payload] = this.serverBoundEncryptionToken.split(".").map(k => Buffer.from(k, 'base64')) 159 | const head = JSON.parse(String(header)) 160 | const body = JSON.parse(String(payload)) 161 | 162 | this.encryptionPubKeyDer = createPublicKey({ 163 | key: Buffer.from(head.x5u, 'base64'), 164 | format: 'der', 165 | type: 'spki', 166 | }) 167 | 168 | this.encryptionSharedSecret = diffieHellman({ 169 | privateKey: this.edchKeyPair.privateKey, 170 | publicKey: this.encryptionPubKeyDer, 171 | }) 172 | 173 | this.encryptionSalt = Buffer.from(body.salt, 'base64') 174 | this.encryptionSecretHash = createHash('sha256') 175 | this.encryptionSecretHash.update(this.encryptionSalt) 176 | this.encryptionSecretHash.update(this.encryptionSharedSecret) 177 | 178 | this.encryptionSecretKeyBytes = this.encryptionSecretHash.digest() 179 | this.encryptionIV = this.encryptionSecretKeyBytes.slice(0, 16) 180 | 181 | this.packetHandler.startEncryption(this.encryptionIV, this.encryptionSecretKeyBytes) 182 | } 183 | public async authMc(xstsResponse: AuthHandlerXSTSResponse): Promise { 184 | return new Promise((r,j) => { 185 | if (!this.mcAuthChains.length) { 186 | this.makeRestRequest('post', C.Endpoints.Misc.MinecraftAuth, C.MinecraftAuthHeaders(createXBLToken(xstsResponse)), { identityPublicKey: this.X509 }) 187 | .then((res) => { 188 | this.mcAuthChains = res.chain 189 | r(true) 190 | }) 191 | .catch((err) => { 192 | j(err) 193 | }) 194 | } else { 195 | r(true) 196 | } 197 | }) 198 | 199 | } 200 | public updateXboxUserData(): void { 201 | if (this.mcAuthChains[1]) { 202 | const userData = this.mcAuthChains[1] 203 | const payload = userData.split(".").map(d => Buffer.from(d, 'base64'))[1] 204 | this._xboxProfile = JSON.parse(String(payload)) 205 | } 206 | } 207 | private _generateClientIdentityChain(): void { 208 | const privateKey = this.edchKeyPair.privateKey 209 | this.clientIdentityChain = JWT.sign({ 210 | identityPublicKey: this.PUBLIC_KEY_ONLINE, 211 | certificateAuthority: true, 212 | }, privateKey as unknown as JWT.Secret, { 213 | algorithm: this.ALGORITHM, 214 | header: { 215 | x5u: this.X509, 216 | alg: this.ALGORITHM, 217 | }, 218 | }) 219 | } 220 | private _generateClientUserChain(payload: Record): void { 221 | const privateKey = this.edchKeyPair.privateKey 222 | this.clientUserChain = JWT.sign(payload, privateKey as unknown as JWT.Secret, { 223 | algorithm: this.ALGORITHM, 224 | header: { 225 | x5u: this.X509, 226 | alg: this.ALGORITHM, 227 | }, 228 | noTimestamp: true, 229 | }) 230 | } 231 | public createLoginPayload(): packet_login { 232 | const skinData = JSON.parse( 233 | DataProvider 234 | .getDataMap() 235 | .getFile('steve.json') 236 | .toString('utf-8')) 237 | const skinBin = DataProvider 238 | .getDataMap() 239 | .getFile('steveSkin.bin') 240 | .toString('base64') 241 | const skinGeometry = DataProvider 242 | .getDataMap() 243 | .getFile('steveGeometry.json') 244 | .toString('base64') 245 | 246 | const payload = { 247 | ...skinData, 248 | 249 | ClientRandomId: Date.now(), 250 | CurrentInputMode: 1, 251 | DefaultInputMode: 1, 252 | DeviceId: nextUUID(), 253 | DeviceModel: 'BeRP', 254 | DeviceOS: 7, 255 | GameVersion: this.version, 256 | GuiScale: -1, 257 | LanguageCode: 'en_US', 258 | PlatformOfflineId: '', 259 | PlatformOnlineId: '', 260 | PlayFabId: nextUUID() 261 | .replace(/-/g, "") 262 | .slice(0, 16), 263 | SelfSignedId: nextUUID(), 264 | ServerAddress: `${this.host}:${this.port}`, 265 | SkinData: skinBin, 266 | SkinGeometryData: skinGeometry, 267 | SkinGeometryVersion: "1.14.0", 268 | ThirdPartyName: this._xboxProfile.extraData.displayName, 269 | ThirdPartyNameOnly: false, 270 | UIProfile: 0, 271 | } 272 | 273 | this._generateClientUserChain(payload) 274 | 275 | const chain = [ 276 | this.clientIdentityChain, 277 | ...this.mcAuthChains, 278 | ] 279 | 280 | const encodedChain = JSON.stringify({ chain }) 281 | 282 | return { 283 | protocol_version: C.CUR_VERSION_PROTOCOL, 284 | tokens: { 285 | identity: encodedChain, 286 | client: this.clientUserChain, 287 | }, 288 | } 289 | } 290 | public async sendPacket(name: K, params: ServerBoundPackets[K][0]): Promise<{ name: K, params: ServerBoundPackets[K][0] }> { 291 | try { 292 | const newPacket = await this.packetHandler.createPacket(name, params) 293 | this._raknet.writeRaw(newPacket) 294 | 295 | return { 296 | name, 297 | params, 298 | } 299 | } catch (error) { 300 | this._logger.error("Failed to create outbound packet:", error) 301 | throw error 302 | } 303 | } 304 | public makeRestRequest(method: Method, url: string, headers?: { [k: string]: any }, data?: { [k: string]: any }): Promise { 305 | return new Promise((r,j) => { 306 | Axios({ 307 | method, 308 | url, 309 | headers, 310 | data, 311 | }) 312 | .then(({ data }) => { 313 | r(data) 314 | }) 315 | .catch((err) => { 316 | j(err) 317 | }) 318 | }) 319 | 320 | } 321 | } 322 | --------------------------------------------------------------------------------