Compare commits
77 commits
next
...
worker-rew
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64fa0a9b8a | ||
|
|
849a6d3c60 | ||
|
|
9057d3acb5 | ||
|
|
547658f489 | ||
|
|
bf9c47dd26 | ||
|
|
9cede6dbbc | ||
|
|
6a5ac4f8d2 | ||
|
|
2630a57d35 | ||
|
|
20569747ca | ||
|
|
9888bd55c1 | ||
|
|
b501893ab2 | ||
|
|
e2b78333a1 | ||
|
|
e917764b76 | ||
|
|
ed041972c4 | ||
|
|
b579ee1767 | ||
|
|
d450a31547 | ||
|
|
853e0e1d84 | ||
|
|
8ddac97414 | ||
|
|
5eedb3c456 | ||
|
|
11abbfcbb1 | ||
|
|
8ee4dc37e7 | ||
|
|
dc2ad7ccce | ||
|
|
b483923009 | ||
|
|
82d0638eb8 | ||
|
|
cae2b612ba | ||
|
|
f88e9c8b61 | ||
|
|
136b051695 | ||
|
|
9fedafe776 | ||
|
|
ccb00043cf | ||
|
|
c1a7765fcb | ||
|
|
de3eddad89 | ||
|
|
e851f4fac2 | ||
|
|
f2f1c2538e | ||
|
|
766d7950f7 | ||
|
|
5364085030 | ||
|
|
400f5982be | ||
|
|
dc073cd559 | ||
|
|
6eb50cde24 | ||
|
|
67d90a56fb | ||
|
|
f2307632a2 | ||
|
|
ddd58399da | ||
|
|
d74d860726 | ||
|
|
4d4637f710 | ||
|
|
c4c76d46aa | ||
|
|
333db054ea | ||
|
|
847314d50f | ||
|
|
8a3c84745d | ||
|
|
3a9e2aa384 | ||
|
|
7cc562bd02 | ||
|
|
f1a5b7cfa6 | ||
|
|
cecc6fbd81 | ||
|
|
c228b91d2d | ||
|
|
75129e48d1 | ||
|
|
bba548e118 | ||
|
|
37b871a8da | ||
|
|
ad862e557b | ||
|
|
0597a3dad2 | ||
|
|
cefdf5362f | ||
|
|
d197859d47 | ||
|
|
1861edf567 | ||
|
|
d8294d565b | ||
|
|
4381ef4f75 | ||
|
|
c65db9a8cb | ||
|
|
34972e4e71 | ||
|
|
638dd6711e |
||
|
|
9356daaefc |
||
|
|
fb10179691 | ||
|
|
7e74633c14 | ||
|
|
2d7ec12a75 | ||
|
|
c626d105ff | ||
|
|
8db6b5bb51 | ||
|
|
2848ab63d3 | ||
|
|
537658476d | ||
|
|
044153c2dc | ||
|
|
380c21486b | ||
|
|
aed5b40516 | ||
|
|
3051cc35f5 |
21 changed files with 5253 additions and 5408 deletions
|
|
@ -158,6 +158,7 @@
|
||||||
"unicorn/prefer-ternary": "off",
|
"unicorn/prefer-ternary": "off",
|
||||||
"unicorn/switch-case-braces": "off",
|
"unicorn/switch-case-braces": "off",
|
||||||
"@typescript-eslint/consistent-type-definitions": "off",
|
"@typescript-eslint/consistent-type-definitions": "off",
|
||||||
|
"unicorn/relative-url-style": "off",
|
||||||
"unicorn/explicit-length-check": "off",
|
"unicorn/explicit-length-check": "off",
|
||||||
"unicorn/prefer-dom-node-append": "off",
|
"unicorn/prefer-dom-node-append": "off",
|
||||||
"typescript-eslint/no-confusing-void-expression": "off",
|
"typescript-eslint/no-confusing-void-expression": "off",
|
||||||
|
|
|
||||||
15
config.json
15
config.json
|
|
@ -10,16 +10,15 @@
|
||||||
"ip": "wss://mcraft.ryzyn.xyz",
|
"ip": "wss://mcraft.ryzyn.xyz",
|
||||||
"version": "1.19.4"
|
"version": "1.19.4"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ip": "grim.mcraft.fun",
|
||||||
|
"version": "1.19.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": "play.hypixel.net"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ip": "wss://play.mcraft.fun"
|
"ip": "wss://play.mcraft.fun"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ip": "wss://play2.mcraft.fun"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ip": "kaboom.pw",
|
|
||||||
"version": "1.20.3",
|
|
||||||
"description": "Very nice a polite server. Must try for everyone!"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"pauseLinks": [
|
"pauseLinks": [
|
||||||
|
|
|
||||||
14
package.json
14
package.json
|
|
@ -118,11 +118,11 @@
|
||||||
"workbox-build": "^7.0.0"
|
"workbox-build": "^7.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rsbuild/core": "^1.0.1-beta.9",
|
"@rsbuild/core": "1.0.1-beta.9",
|
||||||
"@rsbuild/plugin-node-polyfill": "^1.0.3",
|
"@rsbuild/plugin-node-polyfill": "1.0.3",
|
||||||
"@rsbuild/plugin-react": "^1.0.1-beta.9",
|
"@rsbuild/plugin-react": "1.0.1-beta.9",
|
||||||
"@rsbuild/plugin-type-check": "^1.0.1-beta.9",
|
"@rsbuild/plugin-type-check": "1.0.1-beta.9",
|
||||||
"@rsbuild/plugin-typed-css-modules": "^1.0.1",
|
"@rsbuild/plugin-typed-css-modules": "1.0.1",
|
||||||
"@storybook/addon-essentials": "^7.4.6",
|
"@storybook/addon-essentials": "^7.4.6",
|
||||||
"@storybook/addon-links": "^7.4.6",
|
"@storybook/addon-links": "^7.4.6",
|
||||||
"@storybook/blocks": "^7.4.6",
|
"@storybook/blocks": "^7.4.6",
|
||||||
|
|
@ -153,7 +153,7 @@
|
||||||
"mc-assets": "^0.2.45",
|
"mc-assets": "^0.2.45",
|
||||||
"mineflayer-mouse": "^0.1.7",
|
"mineflayer-mouse": "^0.1.7",
|
||||||
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
|
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
|
||||||
"mineflayer": "github:zardoy/mineflayer",
|
"mineflayer": "github:GenerelSchwerz/mineflayer",
|
||||||
"mineflayer-pathfinder": "^2.4.4",
|
"mineflayer-pathfinder": "^2.4.4",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
|
|
@ -194,9 +194,9 @@
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
"@nxg-org/mineflayer-physics-util": "latest",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"vec3": "0.1.10",
|
"vec3": "0.1.10",
|
||||||
"@nxg-org/mineflayer-physics-util": "1.5.8",
|
|
||||||
"three": "0.154.0",
|
"three": "0.154.0",
|
||||||
"diamond-square": "github:zardoy/diamond-square",
|
"diamond-square": "github:zardoy/diamond-square",
|
||||||
"prismarine-block": "github:zardoy/prismarine-block#next-era",
|
"prismarine-block": "github:zardoy/prismarine-block#next-era",
|
||||||
|
|
|
||||||
10024
pnpm-lock.yaml
generated
10024
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,6 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
||||||
// eslint-disable-next-line import/no-named-as-default
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import GUI from 'lil-gui'
|
import GUI from 'lil-gui'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { toMajorVersion } from '../../src/utils'
|
|
||||||
import { WorldDataEmitter } from '../viewer'
|
import { WorldDataEmitter } from '../viewer'
|
||||||
import { Viewer } from '../viewer/lib/viewer'
|
import { Viewer } from '../viewer/lib/viewer'
|
||||||
import { BlockNames } from '../../src/mcDataTypes'
|
import { BlockNames } from '../../src/mcDataTypes'
|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,9 @@ export function sectionPos (pos: { x: number, y: number, z: number }) {
|
||||||
const z = Math.floor(pos.z / 16)
|
const z = Math.floor(pos.z / 16)
|
||||||
return [x, y, z]
|
return [x, y, z]
|
||||||
}
|
}
|
||||||
|
// doesn't support snapshots
|
||||||
|
|
||||||
|
export const toMajorVersion = version => {
|
||||||
|
const [a, b] = (String(version)).split('.')
|
||||||
|
return `${a}.${b}`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,12 @@ import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer'
|
||||||
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
||||||
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
|
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
|
||||||
import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs'
|
import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs'
|
||||||
import { toMajorVersion } from '../../../src/utils'
|
|
||||||
import { ResourcesManager } from '../../../src/resourcesManager'
|
import { ResourcesManager } from '../../../src/resourcesManager'
|
||||||
import { DisplayWorldOptions, RendererReactiveState } from '../../../src/appViewer'
|
import { DisplayWorldOptions, RendererReactiveState } from '../../../src/appViewer'
|
||||||
import { SoundSystem } from '../three/threeJsSound'
|
import { SoundSystem } from '../three/threeJsSound'
|
||||||
import { buildCleanupDecorator } from './cleanupDecorator'
|
import { buildCleanupDecorator } from './cleanupDecorator'
|
||||||
import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './mesher/shared'
|
import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './mesher/shared'
|
||||||
import { chunkPos } from './simpleUtils'
|
import { chunkPos, toMajorVersion } from './simpleUtils'
|
||||||
import { removeStat, updateStatText } from './ui/newStats'
|
import { removeStat, updateStatText } from './ui/newStats'
|
||||||
import { WorldDataEmitter } from './worldDataEmitter'
|
import { WorldDataEmitter } from './worldDataEmitter'
|
||||||
import { IPlayerState } from './basePlayerState'
|
import { IPlayerState } from './basePlayerState'
|
||||||
|
|
|
||||||
121
src/controls.ts
121
src/controls.ts
|
|
@ -712,142 +712,32 @@ document.addEventListener('visibilitychange', (e) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// #region creative fly
|
const isFlying = () => (bot.entity as any).flying
|
||||||
// these controls are more like for gamemode 3
|
|
||||||
|
|
||||||
const makeInterval = (fn, interval) => {
|
|
||||||
const intervalId = setInterval(fn, interval)
|
|
||||||
|
|
||||||
const cleanup = () => {
|
|
||||||
clearInterval(intervalId)
|
|
||||||
cleanup.active = false
|
|
||||||
}
|
|
||||||
cleanup.active = true
|
|
||||||
return cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFlying = () => bot.physics.gravity === 0
|
|
||||||
let endFlyLoop: ReturnType<typeof makeInterval> | undefined
|
|
||||||
|
|
||||||
const currentFlyVector = new Vec3(0, 0, 0)
|
|
||||||
window.currentFlyVector = currentFlyVector
|
|
||||||
|
|
||||||
// todo cleanup
|
|
||||||
const flyingPressedKeys = {
|
|
||||||
down: false,
|
|
||||||
up: false
|
|
||||||
}
|
|
||||||
|
|
||||||
const startFlyLoop = () => {
|
|
||||||
if (!isFlying()) return
|
|
||||||
endFlyLoop?.()
|
|
||||||
|
|
||||||
endFlyLoop = makeInterval(() => {
|
|
||||||
if (!bot) {
|
|
||||||
endFlyLoop?.()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.entity.position.add(currentFlyVector.clone().multiply(new Vec3(0, 0.5, 0)))
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo we will get rid of patching it when refactor controls
|
|
||||||
let originalSetControlState
|
|
||||||
const patchedSetControlState = (action, state) => {
|
|
||||||
if (!isFlying()) {
|
|
||||||
return originalSetControlState(action, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionPerFlyVector = {
|
|
||||||
jump: new Vec3(0, 1, 0),
|
|
||||||
sneak: new Vec3(0, -1, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeVec = actionPerFlyVector[action]
|
|
||||||
if (!changeVec) {
|
|
||||||
return originalSetControlState(action, state)
|
|
||||||
}
|
|
||||||
if (flyingPressedKeys[state === 'jump' ? 'up' : 'down'] === state) return
|
|
||||||
const toAddVec = changeVec.scaled(state ? 1 : -1)
|
|
||||||
for (const coord of ['x', 'y', 'z']) {
|
|
||||||
if (toAddVec[coord] === 0) continue
|
|
||||||
if (currentFlyVector[coord] === toAddVec[coord]) return
|
|
||||||
}
|
|
||||||
currentFlyVector.add(toAddVec)
|
|
||||||
flyingPressedKeys[state === 'jump' ? 'up' : 'down'] = state
|
|
||||||
}
|
|
||||||
|
|
||||||
const startFlying = (sendAbilities = true) => {
|
const startFlying = (sendAbilities = true) => {
|
||||||
bot.entity['creativeFly'] = true
|
|
||||||
if (sendAbilities) {
|
if (sendAbilities) {
|
||||||
bot._client.write('abilities', {
|
bot._client.write('abilities', {
|
||||||
flags: 2,
|
flags: 2,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// window.flyingSpeed will be removed
|
(bot.entity as any).flying = true
|
||||||
bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 // todo use abilities
|
|
||||||
bot.entity.velocity = new Vec3(0, 0, 0)
|
|
||||||
bot.creative.startFlying()
|
|
||||||
startFlyLoop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const endFlying = (sendAbilities = true) => {
|
const endFlying = (sendAbilities = true) => {
|
||||||
bot.entity['creativeFly'] = false
|
if (!isFlying()) return
|
||||||
if (bot.physics.gravity !== 0) return
|
|
||||||
if (sendAbilities) {
|
if (sendAbilities) {
|
||||||
bot._client.write('abilities', {
|
bot._client.write('abilities', {
|
||||||
flags: 0,
|
flags: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Object.assign(flyingPressedKeys, {
|
(bot.entity as any).flying = false
|
||||||
up: false,
|
|
||||||
down: false
|
|
||||||
})
|
|
||||||
currentFlyVector.set(0, 0, 0)
|
|
||||||
bot.physics['airborneAcceleration'] = standardAirborneAcceleration
|
|
||||||
bot.creative.stopFlying()
|
|
||||||
endFlyLoop?.()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let allowFlying = false
|
|
||||||
|
|
||||||
export const onBotCreate = () => {
|
export const onBotCreate = () => {
|
||||||
let wasSpectatorFlying = false
|
|
||||||
bot._client.on('abilities', ({ flags }) => {
|
|
||||||
allowFlying = !!(flags & 4)
|
|
||||||
if (flags & 2) { // flying
|
|
||||||
toggleFly(true, false)
|
|
||||||
} else {
|
|
||||||
toggleFly(false, false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const gamemodeCheck = () => {
|
|
||||||
if (bot.game.gameMode === 'spectator') {
|
|
||||||
allowFlying = true
|
|
||||||
toggleFly(true, false)
|
|
||||||
wasSpectatorFlying = true
|
|
||||||
} else if (wasSpectatorFlying) {
|
|
||||||
toggleFly(false, false)
|
|
||||||
wasSpectatorFlying = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bot.on('game', () => {
|
|
||||||
gamemodeCheck()
|
|
||||||
})
|
|
||||||
bot.on('login', () => {
|
|
||||||
gamemodeCheck()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const standardAirborneAcceleration = 0.02
|
|
||||||
const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => {
|
const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => {
|
||||||
// if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return
|
if (!bot.entity.canFly) return
|
||||||
if (!allowFlying) return
|
|
||||||
if (bot.setControlState !== patchedSetControlState) {
|
|
||||||
originalSetControlState = bot.setControlState
|
|
||||||
bot.setControlState = patchedSetControlState
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newState) {
|
if (newState) {
|
||||||
startFlying(sendAbilities)
|
startFlying(sendAbilities)
|
||||||
|
|
@ -856,7 +746,6 @@ const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => {
|
||||||
}
|
}
|
||||||
gameAdditionalState.isFlying = isFlying()
|
gameAdditionalState.isFlying = isFlying()
|
||||||
}
|
}
|
||||||
// #endregion
|
|
||||||
|
|
||||||
const selectItem = async () => {
|
const selectItem = async () => {
|
||||||
const block = bot.blockAtCursor(5)
|
const block = bot.blockAtCursor(5)
|
||||||
|
|
|
||||||
117
src/index.ts
117
src/index.ts
|
|
@ -17,8 +17,6 @@ import './mineflayer/timers'
|
||||||
import { getServerInfo } from './mineflayer/mc-protocol'
|
import { getServerInfo } from './mineflayer/mc-protocol'
|
||||||
import { onGameLoad } from './inventoryWindows'
|
import { onGameLoad } from './inventoryWindows'
|
||||||
import initCollisionShapes from './getCollisionInteractionShapes'
|
import initCollisionShapes from './getCollisionInteractionShapes'
|
||||||
import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth'
|
|
||||||
import microsoftAuthflow from './microsoftAuthflow'
|
|
||||||
import { Duplex } from 'stream'
|
import { Duplex } from 'stream'
|
||||||
|
|
||||||
import './scaleInterface'
|
import './scaleInterface'
|
||||||
|
|
@ -75,7 +73,7 @@ import { saveToBrowserMemory } from './react/PauseScreen'
|
||||||
import './devReload'
|
import './devReload'
|
||||||
import './water'
|
import './water'
|
||||||
import { ConnectOptions, loadMinecraftData, getVersionAutoSelect, downloadOtherGameData, downloadAllMinecraftData } from './connect'
|
import { ConnectOptions, loadMinecraftData, getVersionAutoSelect, downloadOtherGameData, downloadAllMinecraftData } from './connect'
|
||||||
import { ref, subscribe } from 'valtio'
|
import { subscribe } from 'valtio'
|
||||||
import { signInMessageState } from './react/SignInMessageProvider'
|
import { signInMessageState } from './react/SignInMessageProvider'
|
||||||
import { updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage'
|
import { updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage'
|
||||||
import packetsPatcher from './mineflayer/plugins/packetsPatcher'
|
import packetsPatcher from './mineflayer/plugins/packetsPatcher'
|
||||||
|
|
@ -97,6 +95,7 @@ import { createConsoleLogProgressReporter, createFullScreenProgressReporter, Pro
|
||||||
import { appViewer } from './appViewer'
|
import { appViewer } from './appViewer'
|
||||||
import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend'
|
import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend'
|
||||||
import { subscribeKey } from 'valtio/utils'
|
import { subscribeKey } from 'valtio/utils'
|
||||||
|
import { getProtocolClientGetter } from './protocolWorker/protocolMain'
|
||||||
|
|
||||||
window.debug = debug
|
window.debug = debug
|
||||||
window.beforeRenderFrame = []
|
window.beforeRenderFrame = []
|
||||||
|
|
@ -190,8 +189,8 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
|
|
||||||
const { renderDistance: renderDistanceSingleplayer, multiplayerRenderDistance } = options
|
const { renderDistance: renderDistanceSingleplayer, multiplayerRenderDistance } = options
|
||||||
|
|
||||||
const parsedServer = parseServerAddress(connectOptions.server)
|
const serverParsed = parseServerAddress(connectOptions.server)
|
||||||
const server = { host: parsedServer.host, port: parsedServer.port }
|
const server = { host: serverParsed.host, port: serverParsed.port }
|
||||||
if (connectOptions.proxy?.startsWith(':')) {
|
if (connectOptions.proxy?.startsWith(':')) {
|
||||||
connectOptions.proxy = `${location.protocol}//${location.hostname}${connectOptions.proxy}`
|
connectOptions.proxy = `${location.protocol}//${location.hostname}${connectOptions.proxy}`
|
||||||
}
|
}
|
||||||
|
|
@ -199,12 +198,12 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
const https = connectOptions.proxy.startsWith('https://') || location.protocol === 'https:'
|
const https = connectOptions.proxy.startsWith('https://') || location.protocol === 'https:'
|
||||||
connectOptions.proxy = `${connectOptions.proxy}:${https ? 443 : 80}`
|
connectOptions.proxy = `${connectOptions.proxy}:${https ? 443 : 80}`
|
||||||
}
|
}
|
||||||
const parsedProxy = parseServerAddress(connectOptions.proxy, false)
|
const proxyParsed = parseServerAddress(connectOptions.proxy, false)
|
||||||
const proxy = { host: parsedProxy.host, port: parsedProxy.port }
|
const proxy = { host: proxyParsed.host, port: proxyParsed.port }
|
||||||
let { username } = connectOptions
|
let { username } = connectOptions
|
||||||
|
|
||||||
if (connectOptions.server) {
|
if (connectOptions.server) {
|
||||||
console.log(`connecting to ${server.host}:${server.port ?? 25_565}`)
|
console.log(`connecting to ${serverParsed.serverIpFull}`)
|
||||||
}
|
}
|
||||||
console.log('using player username', username)
|
console.log('using player username', username)
|
||||||
|
|
||||||
|
|
@ -244,7 +243,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let lastPacket = undefined as string | undefined
|
const lastPacket = undefined as string | undefined
|
||||||
const onPossibleErrorDisconnect = () => {
|
const onPossibleErrorDisconnect = () => {
|
||||||
if (lastPacket && bot?._client && bot._client.state !== states.PLAY) {
|
if (lastPacket && bot?._client && bot._client.state !== states.PLAY) {
|
||||||
appStatusState.descriptionHint = `Last Server Packet: ${lastPacket}`
|
appStatusState.descriptionHint = `Last Server Packet: ${lastPacket}`
|
||||||
|
|
@ -285,13 +284,12 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
|
|
||||||
let clientDataStream: Duplex | undefined
|
let clientDataStream: Duplex | undefined
|
||||||
|
|
||||||
if (connectOptions.server && !connectOptions.viewerWsConnect && !parsedServer.isWebSocket) {
|
if (connectOptions.server && !connectOptions.viewerWsConnect && !serverParsed.isWebSocket) {
|
||||||
console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`)
|
console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`)
|
||||||
net['setProxy']({ hostname: proxy.host, port: proxy.port })
|
net['setProxy']({ hostname: proxy.host, port: proxy.port })
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance
|
const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance
|
||||||
let updateDataAfterJoin = () => { }
|
|
||||||
let localServer
|
let localServer
|
||||||
let localReplaySession: ReturnType<typeof startLocalReplayServer> | undefined
|
let localReplaySession: ReturnType<typeof startLocalReplayServer> | undefined
|
||||||
try {
|
try {
|
||||||
|
|
@ -411,23 +409,11 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
}
|
}
|
||||||
setLoadingScreenStatus(initialLoadingText)
|
setLoadingScreenStatus(initialLoadingText)
|
||||||
|
|
||||||
if (parsedServer.isWebSocket) {
|
if (serverParsed.isWebSocket) {
|
||||||
clientDataStream = (await getWebsocketStream(server.host)).mineflayerStream
|
clientDataStream = (await getWebsocketStream(server.host)).mineflayerStream
|
||||||
}
|
}
|
||||||
|
|
||||||
let newTokensCacheResult = null as any
|
|
||||||
const cachedTokens = typeof connectOptions.authenticatedAccount === 'object' ? connectOptions.authenticatedAccount.cachedTokens : {}
|
const cachedTokens = typeof connectOptions.authenticatedAccount === 'object' ? connectOptions.authenticatedAccount.cachedTokens : {}
|
||||||
const authData = connectOptions.authenticatedAccount ? await microsoftAuthflow({
|
|
||||||
tokenCaches: cachedTokens,
|
|
||||||
proxyBaseUrl: connectOptions.proxy,
|
|
||||||
setProgressText (text) {
|
|
||||||
setLoadingScreenStatus(text)
|
|
||||||
},
|
|
||||||
setCacheResult (result) {
|
|
||||||
newTokensCacheResult = result
|
|
||||||
},
|
|
||||||
connectingServer: server.host
|
|
||||||
}) : undefined
|
|
||||||
|
|
||||||
if (p2pMultiplayer) {
|
if (p2pMultiplayer) {
|
||||||
clientDataStream = await connectToPeer(connectOptions.peerId!, connectOptions.peerOptions)
|
clientDataStream = await connectToPeer(connectOptions.peerId!, connectOptions.peerOptions)
|
||||||
|
|
@ -458,9 +444,13 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
await downloadMcData(finalVersion)
|
await downloadMcData(finalVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const brand = clientDataStream ? 'minecraft-web-client' : undefined
|
||||||
|
const createClient = await getProtocolClientGetter(proxy, connectOptions, serverParsed.serverIpFull)
|
||||||
|
|
||||||
bot = mineflayer.createBot({
|
bot = mineflayer.createBot({
|
||||||
host: server.host,
|
host: server.host,
|
||||||
port: server.port ? +server.port : undefined,
|
port: server.port ? +server.port : undefined,
|
||||||
|
brand,
|
||||||
version: finalVersion || false,
|
version: finalVersion || false,
|
||||||
...clientDataStream ? {
|
...clientDataStream ? {
|
||||||
stream: clientDataStream as any,
|
stream: clientDataStream as any,
|
||||||
|
|
@ -477,53 +467,13 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
connect () { },
|
connect () { },
|
||||||
Client: CustomChannelClient as any,
|
Client: CustomChannelClient as any,
|
||||||
} : {},
|
} : {},
|
||||||
onMsaCode (data) {
|
get client () {
|
||||||
signInMessageState.code = data.user_code
|
if (clientDataStream || singleplayer || p2pMultiplayer || localReplaySession || connectOptions.viewerWsConnect) {
|
||||||
signInMessageState.link = data.verification_uri
|
return undefined
|
||||||
signInMessageState.expiresOn = Date.now() + data.expires_in * 1000
|
|
||||||
},
|
|
||||||
sessionServer: authData?.sessionEndpoint?.toString(),
|
|
||||||
auth: connectOptions.authenticatedAccount ? async (client, options) => {
|
|
||||||
authData!.setOnMsaCodeCallback(options.onMsaCode)
|
|
||||||
authData?.setConnectingVersion(client.version)
|
|
||||||
//@ts-expect-error
|
|
||||||
client.authflow = authData!.authFlow
|
|
||||||
try {
|
|
||||||
signInMessageState.abortController = ref(new AbortController())
|
|
||||||
await Promise.race([
|
|
||||||
protocolMicrosoftAuth.authenticate(client, options),
|
|
||||||
new Promise((_r, reject) => {
|
|
||||||
signInMessageState.abortController.signal.addEventListener('abort', () => {
|
|
||||||
reject(new UserError('Aborted by user'))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
])
|
|
||||||
if (signInMessageState.shouldSaveToken) {
|
|
||||||
updateAuthenticatedAccountData(accounts => {
|
|
||||||
const existingAccount = accounts.find(a => a.username === client.username)
|
|
||||||
if (existingAccount) {
|
|
||||||
existingAccount.cachedTokens = { ...existingAccount.cachedTokens, ...newTokensCacheResult }
|
|
||||||
} else {
|
|
||||||
accounts.push({
|
|
||||||
username: client.username,
|
|
||||||
cachedTokens: { ...cachedTokens, ...newTokensCacheResult }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return accounts
|
|
||||||
})
|
|
||||||
updateDataAfterJoin = () => {
|
|
||||||
updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: client.username }), connectOptions.serverIndex)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updateDataAfterJoin = () => {
|
|
||||||
updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: undefined }), connectOptions.serverIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setLoadingScreenStatus('Authentication successful. Logging in to server')
|
|
||||||
} finally {
|
|
||||||
signInMessageState.code = ''
|
|
||||||
}
|
}
|
||||||
} : undefined,
|
return createClient.call(this)
|
||||||
|
},
|
||||||
|
// auth: connectOptions.authenticatedAccount ? : undefined,
|
||||||
username,
|
username,
|
||||||
viewDistance: renderDistance,
|
viewDistance: renderDistance,
|
||||||
checkTimeoutInterval: 240 * 1000,
|
checkTimeoutInterval: 240 * 1000,
|
||||||
|
|
@ -579,16 +529,16 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// socket setup actually can be delayed because of dns lookup
|
// socket setup actually can be delayed because of dns lookup
|
||||||
if (bot._client.socket) {
|
// if (bot._client.socket) {
|
||||||
setupConnectHandlers()
|
// setupConnectHandlers()
|
||||||
} else {
|
// } else {
|
||||||
const originalSetSocket = bot._client.setSocket.bind(bot._client)
|
// const originalSetSocket = bot._client.setSocket.bind(bot._client)
|
||||||
bot._client.setSocket = (socket) => {
|
// bot._client.setSocket = (socket) => {
|
||||||
if (!bot) return
|
// if (!bot) return
|
||||||
originalSetSocket(socket)
|
// originalSetSocket(socket)
|
||||||
setupConnectHandlers()
|
// setupConnectHandlers()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -623,7 +573,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
// bot.emit('kicked', '{"translate":"disconnect.genericReason","with":["Internal Exception: io.netty.handler.codec.EncoderException: com.viaversion.viaversion.exception.InformativeException: Please report this on the Via support Discord or open an issue on the relevant GitHub repository\\nPacket Type: SYSTEM_CHAT, Index: 1, Type: TagType, Data: [], Packet ID: 103, Source 0: com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.Protocol1_20_3To1_20_2$$Lambda/0x00007f9930f63080"]}', false)
|
// bot.emit('kicked', '{"translate":"disconnect.genericReason","with":["Internal Exception: io.netty.handler.codec.EncoderException: com.viaversion.viaversion.exception.InformativeException: Please report this on the Via support Discord or open an issue on the relevant GitHub repository\\nPacket Type: SYSTEM_CHAT, Index: 1, Type: TagType, Data: [], Packet ID: 103, Source 0: com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.Protocol1_20_3To1_20_2$$Lambda/0x00007f9930f63080"]}', false)
|
||||||
|
|
||||||
const packetBeforePlay = (_, __, ___, fullBuffer) => {
|
const packetBeforePlay = (_, __, ___, fullBuffer) => {
|
||||||
lastPacket = fullBuffer.toString()
|
// lastPacket = fullBuffer.toString()
|
||||||
}
|
}
|
||||||
bot._client.on('packet', packetBeforePlay as any)
|
bot._client.on('packet', packetBeforePlay as any)
|
||||||
const playStateSwitch = (newState) => {
|
const playStateSwitch = (newState) => {
|
||||||
|
|
@ -675,6 +625,10 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
document.dispatchEvent(new Event('cypress-world-ready'))
|
document.dispatchEvent(new Event('cypress-world-ready'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!connectOptions.worldStateFileContents || connectOptions.worldStateFileContents.length < 3 * 1024 * 1024) {
|
||||||
|
localStorage.lastConnectOptions = JSON.stringify(connectOptions)
|
||||||
|
}
|
||||||
|
|
||||||
const spawnEarlier = !singleplayer && !p2pMultiplayer
|
const spawnEarlier = !singleplayer && !p2pMultiplayer
|
||||||
// don't use spawn event, player can be dead
|
// don't use spawn event, player can be dead
|
||||||
bot.once(spawnEarlier ? 'forcedMove' : 'health', async () => {
|
bot.once(spawnEarlier ? 'forcedMove' : 'health', async () => {
|
||||||
|
|
@ -703,7 +657,6 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
localStorage.removeItem('lastConnectOptions')
|
localStorage.removeItem('lastConnectOptions')
|
||||||
}
|
}
|
||||||
connectOptions.onSuccessfulPlay?.()
|
connectOptions.onSuccessfulPlay?.()
|
||||||
updateDataAfterJoin()
|
|
||||||
if (connectOptions.autoLoginPassword) {
|
if (connectOptions.autoLoginPassword) {
|
||||||
bot.chat(`/login ${connectOptions.autoLoginPassword}`)
|
bot.chat(`/login ${connectOptions.autoLoginPassword}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
||||||
|
|
||||||
|
customEvents.on('hurtAnimation', () => {
|
||||||
|
cameraShake.shakeFromDamage()
|
||||||
|
})
|
||||||
|
|
||||||
customEvents.on('mineflayerBotCreated', () => {
|
customEvents.on('mineflayerBotCreated', () => {
|
||||||
customEvents.on('hurtAnimation', (yaw) => {
|
customEvents.on('hurtAnimation', (yaw) => {
|
||||||
getThreeJsRendererMethods()?.shakeFromDamage()
|
getThreeJsRendererMethods()?.shakeFromDamage()
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,10 @@ import { getWebsocketStream } from './websocket-core'
|
||||||
|
|
||||||
let lastPacketTime = 0
|
let lastPacketTime = 0
|
||||||
customEvents.on('mineflayerBotCreated', () => {
|
customEvents.on('mineflayerBotCreated', () => {
|
||||||
// todo move more code here
|
(bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => {
|
||||||
if (!appQueryParams.noPacketsValidation) {
|
lastPacketTime = performance.now()
|
||||||
(bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => {
|
})
|
||||||
validatePacket(packetMeta.name, data, fullBuffer, true)
|
|
||||||
lastPacketTime = performance.now()
|
|
||||||
});
|
|
||||||
(bot._client as unknown as Client).on('writePacket', (name, params) => {
|
|
||||||
validatePacket(name, params, Buffer.alloc(0), false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
import { ref } from 'valtio'
|
||||||
|
import { signInMessageState } from '../react/SignInMessageProvider'
|
||||||
|
import { updateAuthenticatedAccountData, updateLoadedServerData } from '../react/serversStorage'
|
||||||
|
import { setLoadingScreenStatus } from '../appStatus'
|
||||||
|
import { ConnectOptions } from '../connect'
|
||||||
|
import { showNotification } from '../react/NotificationProvider'
|
||||||
|
|
||||||
export const getProxyDetails = async (proxyBaseUrl: string) => {
|
export const getProxyDetails = async (proxyBaseUrl: string) => {
|
||||||
if (!proxyBaseUrl.startsWith('http')) proxyBaseUrl = `${isPageSecure() ? 'https' : 'http'}://${proxyBaseUrl}`
|
if (!proxyBaseUrl.startsWith('http')) proxyBaseUrl = `${isPageSecure() ? 'https' : 'http'}://${proxyBaseUrl}`
|
||||||
const url = `${proxyBaseUrl}/api/vm/net/connect`
|
const url = `${proxyBaseUrl}/api/vm/net/connect`
|
||||||
|
|
@ -10,13 +17,14 @@ export const getProxyDetails = async (proxyBaseUrl: string) => {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { }, setCacheResult, connectingServer }) => {
|
export const getAuthData = async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { }, connectingServer }) => {
|
||||||
let onMsaCodeCallback
|
let onMsaCodeCallback
|
||||||
let connectingVersion = ''
|
let connectingVersion = ''
|
||||||
// const authEndpoint = 'http://localhost:3000/'
|
// const authEndpoint = 'http://localhost:3000/'
|
||||||
// const sessionEndpoint = 'http://localhost:3000/session'
|
// const sessionEndpoint = 'http://localhost:3000/session'
|
||||||
let authEndpoint: URL | undefined
|
let authEndpoint: URL | undefined
|
||||||
let sessionEndpoint: URL | undefined
|
let sessionEndpoint: URL | undefined
|
||||||
|
let newTokensCacheResult = null as any
|
||||||
const result = await getProxyDetails(proxyBaseUrl)
|
const result = await getProxyDetails(proxyBaseUrl)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -32,7 +40,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
|
||||||
async getMinecraftJavaToken () {
|
async getMinecraftJavaToken () {
|
||||||
setProgressText('Authenticating with Microsoft account')
|
setProgressText('Authenticating with Microsoft account')
|
||||||
if (!window.crypto && !isPageSecure()) throw new Error('Crypto API is available only in secure contexts. Be sure to use https!')
|
if (!window.crypto && !isPageSecure()) throw new Error('Crypto API is available only in secure contexts. Be sure to use https!')
|
||||||
let result = null
|
let result = null as any
|
||||||
await fetch(authEndpoint, {
|
await fetch(authEndpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -73,7 +81,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
|
||||||
}
|
}
|
||||||
if (json.error) throw new Error(json.error)
|
if (json.error) throw new Error(json.error)
|
||||||
if (json.token) result = json
|
if (json.token) result = json
|
||||||
if (json.newCache) setCacheResult(json.newCache)
|
if (json.newCache) newTokensCacheResult = json.newCache
|
||||||
}
|
}
|
||||||
|
|
||||||
const strings = decoder.decode(value)
|
const strings = decoder.decode(value)
|
||||||
|
|
@ -86,11 +94,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
|
||||||
}
|
}
|
||||||
return reader.read().then(processText)
|
return reader.read().then(processText)
|
||||||
})
|
})
|
||||||
const restoredData = await restoreData(result)
|
return result
|
||||||
if (restoredData?.certificates?.profileKeys?.privatePEM) {
|
|
||||||
restoredData.certificates.profileKeys.private = restoredData.certificates.profileKeys.privatePEM
|
|
||||||
}
|
|
||||||
return restoredData
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -101,77 +105,68 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
|
||||||
},
|
},
|
||||||
setConnectingVersion (version) {
|
setConnectingVersion (version) {
|
||||||
connectingVersion = version
|
connectingVersion = version
|
||||||
|
},
|
||||||
|
get newTokensCacheResult () {
|
||||||
|
return newTokensCacheResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const authFlowMainThread = async (worker: Worker, authData: Awaited<ReturnType<typeof getAuthData>>, connectOptions: ConnectOptions, setActionAfterJoin: (action: () => void) => void) => {
|
||||||
|
const cachedTokens = typeof connectOptions.authenticatedAccount === 'object' ? connectOptions.authenticatedAccount.cachedTokens : {}
|
||||||
|
signInMessageState.abortController = ref(new AbortController())
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
worker.addEventListener('message', ({ data }) => {
|
||||||
|
if (data.type === 'authFlow') {
|
||||||
|
authData.setConnectingVersion(data.version)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
authData.setOnMsaCodeCallback((codeData) => {
|
||||||
|
signInMessageState.code = codeData.user_code
|
||||||
|
signInMessageState.link = codeData.verification_uri
|
||||||
|
signInMessageState.expiresOn = Date.now() + codeData.expires_in * 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await authData.authFlow.getMinecraftJavaToken()
|
||||||
|
signInMessageState.code = ''
|
||||||
|
if (!data) return
|
||||||
|
const username = data.profile.name
|
||||||
|
if (signInMessageState.shouldSaveToken) {
|
||||||
|
updateAuthenticatedAccountData(accounts => {
|
||||||
|
const existingAccount = accounts.find(a => a.username === username)
|
||||||
|
if (existingAccount) {
|
||||||
|
existingAccount.cachedTokens = { ...existingAccount.cachedTokens, ...authData.newTokensCacheResult }
|
||||||
|
} else {
|
||||||
|
accounts.push({
|
||||||
|
username,
|
||||||
|
cachedTokens: { ...cachedTokens, ...authData.newTokensCacheResult }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
showNotification(`Account ${username} saved`)
|
||||||
|
return accounts
|
||||||
|
})
|
||||||
|
setActionAfterJoin(() => {
|
||||||
|
updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: username }), connectOptions.serverIndex)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setActionAfterJoin(() => {
|
||||||
|
updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: undefined }), connectOptions.serverIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
worker.postMessage({
|
||||||
|
type: 'authflowResult',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
setLoadingScreenStatus('Authentication successful. Logging in to server')
|
||||||
|
}
|
||||||
|
|
||||||
function isPageSecure (url = window.location.href) {
|
function isPageSecure (url = window.location.href) {
|
||||||
return !url.startsWith('http:')
|
return !url.startsWith('http:')
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore dates from strings
|
|
||||||
const restoreData = async (json) => {
|
|
||||||
const promises = [] as Array<Promise<void>>
|
|
||||||
if (typeof json === 'object' && json) {
|
|
||||||
for (const [key, value] of Object.entries(json)) {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
promises.push(tryRestorePublicKey(value, key, json))
|
|
||||||
if (value.endsWith('Z')) {
|
|
||||||
const date = new Date(value)
|
|
||||||
if (!isNaN(date.getTime())) {
|
|
||||||
json[key] = date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await restoreData(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises)
|
|
||||||
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
const tryRestorePublicKey = async (value: string, name: string, parent: { [x: string]: any }) => {
|
|
||||||
value = value.trim()
|
|
||||||
if (!name.endsWith('PEM') || !value.startsWith('-----BEGIN RSA PUBLIC KEY-----') || !value.endsWith('-----END RSA PUBLIC KEY-----')) return
|
|
||||||
const der = pemToArrayBuffer(value)
|
|
||||||
const key = await window.crypto.subtle.importKey(
|
|
||||||
'spki', // Specify that the data is in SPKI format
|
|
||||||
der,
|
|
||||||
{
|
|
||||||
name: 'RSA-OAEP',
|
|
||||||
hash: { name: 'SHA-256' }
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
['encrypt'] // Specify key usages
|
|
||||||
)
|
|
||||||
const originalName = name.replace('PEM', '')
|
|
||||||
const exported = await window.crypto.subtle.exportKey('spki', key)
|
|
||||||
const exportedBuffer = new Uint8Array(exported)
|
|
||||||
parent[originalName] = {
|
|
||||||
export () {
|
|
||||||
return exportedBuffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function pemToArrayBuffer (pem) {
|
|
||||||
// Fetch the part of the PEM string between header and footer
|
|
||||||
const pemHeader = '-----BEGIN RSA PUBLIC KEY-----'
|
|
||||||
const pemFooter = '-----END RSA PUBLIC KEY-----'
|
|
||||||
const pemContents = pem.slice(pemHeader.length, pem.length - pemFooter.length).trim()
|
|
||||||
const binaryDerString = atob(pemContents.replaceAll(/\s/g, ''))
|
|
||||||
const binaryDer = new Uint8Array(binaryDerString.length)
|
|
||||||
for (let i = 0; i < binaryDerString.length; i++) {
|
|
||||||
binaryDer[i] = binaryDerString.codePointAt(i)!
|
|
||||||
}
|
|
||||||
return binaryDer.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlWithBase = (url: string, base: string) => {
|
const urlWithBase = (url: string, base: string) => {
|
||||||
const defaultBase = isPageSecure() ? 'https' : 'http'
|
const defaultBase = isPageSecure() ? 'https' : 'http'
|
||||||
if (!base.startsWith('http')) base = `${defaultBase}://${base}`
|
if (!base.startsWith('http')) base = `${defaultBase}://${base}`
|
||||||
186
src/protocolWorker/protocolMain.ts
Normal file
186
src/protocolWorker/protocolMain.ts
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
import EventEmitter from 'events'
|
||||||
|
import { ClientOptions } from 'minecraft-protocol'
|
||||||
|
import { appQueryParams } from '../appParams'
|
||||||
|
import { ConnectOptions } from '../connect'
|
||||||
|
import { setLoadingScreenStatus } from '../appStatus'
|
||||||
|
import { authFlowMainThread, getAuthData } from './microsoftAuthflow'
|
||||||
|
|
||||||
|
const debug = require('debug')('minecraft-protocol')
|
||||||
|
|
||||||
|
const copyPrimitiveValues = (obj: any, deep = false, ignoreKeys: string[] = []) => {
|
||||||
|
const copy = {} as Record<string, any>
|
||||||
|
for (const key in obj) {
|
||||||
|
if (ignoreKeys.includes(key)) continue
|
||||||
|
if (typeof obj[key] === 'object' && obj[key] !== null && deep) {
|
||||||
|
copy[key] = copyPrimitiveValues(obj[key])
|
||||||
|
} else if (typeof obj[key] === 'number' || typeof obj[key] === 'string' || typeof obj[key] === 'boolean') {
|
||||||
|
copy[key] = obj[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getProtocolClientGetter = async (proxy: { host: string, port?: string }, connectOptions: ConnectOptions, serverIp: string) => {
|
||||||
|
const cachedTokens = typeof connectOptions.authenticatedAccount === 'object' ? connectOptions.authenticatedAccount.cachedTokens : {}
|
||||||
|
const authData = connectOptions.authenticatedAccount ?
|
||||||
|
await getAuthData({
|
||||||
|
tokenCaches: cachedTokens,
|
||||||
|
proxyBaseUrl: connectOptions.proxy,
|
||||||
|
setProgressText (text) {
|
||||||
|
setLoadingScreenStatus(text)
|
||||||
|
},
|
||||||
|
connectingServer: serverIp.replace(/:25565$/, '')
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
function createMinecraftProtocolClient (this: any) {
|
||||||
|
if (!this.brand) return // brand is not resolved yet
|
||||||
|
if (bot?._client) return bot._client
|
||||||
|
const createClientOptions = copyPrimitiveValues(this, false, ['client']) as ClientOptions
|
||||||
|
|
||||||
|
createClientOptions.sessionServer = authData?.sessionEndpoint.toString()
|
||||||
|
|
||||||
|
const worker = new Worker(new URL('./protocolWorker.ts', import.meta.url))
|
||||||
|
setTimeout(() => {
|
||||||
|
if (bot) {
|
||||||
|
bot.on('end', () => {
|
||||||
|
worker.terminate()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
worker.terminate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
type: 'setProxy',
|
||||||
|
hostname: proxy.host,
|
||||||
|
port: proxy.port
|
||||||
|
})
|
||||||
|
worker.postMessage({
|
||||||
|
type: 'init',
|
||||||
|
options: createClientOptions,
|
||||||
|
noPacketsValidation: appQueryParams.noPacketsValidation,
|
||||||
|
useAuthFlow: !!authData
|
||||||
|
})
|
||||||
|
|
||||||
|
const eventEmitter = new EventEmitter() as any
|
||||||
|
eventEmitter.version = this.version
|
||||||
|
|
||||||
|
worker.addEventListener('message', ({ data }) => {
|
||||||
|
if (data.type === 'event') {
|
||||||
|
eventEmitter.emit(data.event, ...data.args)
|
||||||
|
if (data.event === 'packet') {
|
||||||
|
let [packetData, packetMeta] = data.args
|
||||||
|
if (window.stopPacketsProcessing === true || (Array.isArray(window.stopPacketsProcessing) && window.stopPacketsProcessing.includes(packetMeta.name))) {
|
||||||
|
if (window.skipPackets && !window.skipPackets.includes(packetMeta.name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start timing the packet processing
|
||||||
|
const startTime = performance.now()
|
||||||
|
|
||||||
|
// restore transferred data
|
||||||
|
if (packetData instanceof Uint8Array) {
|
||||||
|
packetData = Buffer.from(packetData)
|
||||||
|
} else if (typeof packetData === 'object' && packetData !== null) {
|
||||||
|
// Deep patch any Uint8Array values in the packet data object
|
||||||
|
const patchUint8Arrays = (obj: any) => {
|
||||||
|
for (const key in obj) {
|
||||||
|
if (obj[key] instanceof Uint8Array) {
|
||||||
|
obj[key] = Buffer.from(obj[key])
|
||||||
|
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||||
|
patchUint8Arrays(obj[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patchUint8Arrays(packetData)
|
||||||
|
}
|
||||||
|
|
||||||
|
eventEmitter.state = packetMeta.state
|
||||||
|
debug(`RECV ${eventEmitter.state}:${packetMeta.name}`, packetData)
|
||||||
|
|
||||||
|
// Initialize packet timing tracking if not exists
|
||||||
|
if (!window.packetTimings) {
|
||||||
|
window.packetTimings = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.packetTimings[packetMeta.name]) {
|
||||||
|
window.packetTimings[packetMeta.name] = {
|
||||||
|
total: 0,
|
||||||
|
count: 0,
|
||||||
|
avg: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventEmitter.emit(packetMeta.name, packetData, packetMeta)
|
||||||
|
|
||||||
|
// Calculate processing time
|
||||||
|
const processingTime = performance.now() - startTime
|
||||||
|
window.packetTimings[packetMeta.name].total += processingTime
|
||||||
|
window.packetTimings[packetMeta.name].count++
|
||||||
|
window.packetTimings[packetMeta.name].avg =
|
||||||
|
window.packetTimings[packetMeta.name].total / window.packetTimings[packetMeta.name].count
|
||||||
|
|
||||||
|
// Update packetsThreadBlocking every second
|
||||||
|
if (!window.lastStatsUpdate) {
|
||||||
|
window.lastStatsUpdate = Date.now()
|
||||||
|
setInterval(() => {
|
||||||
|
// Sort by total processing time
|
||||||
|
window.packetsThreadBlocking = Object.entries(window.packetTimings)
|
||||||
|
.sort(([, a], [, b]) => b.total - a.total)
|
||||||
|
.reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = value
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
// Reset timings for next interval
|
||||||
|
window.packetTimings = {}
|
||||||
|
window.lastStatsUpdate = Date.now()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventEmitter.on('writePacket', (...args: any[]) => {
|
||||||
|
debug(`SEND ${eventEmitter.state}:${args[0]}`, ...args.slice(1))
|
||||||
|
})
|
||||||
|
|
||||||
|
const redirectMethodsToWorker = (names: string[]) => {
|
||||||
|
for (const name of names) {
|
||||||
|
eventEmitter[name] = async (...args: any[]) => {
|
||||||
|
worker.postMessage({
|
||||||
|
type: 'call',
|
||||||
|
name,
|
||||||
|
args: JSON.parse(JSON.stringify(args))
|
||||||
|
})
|
||||||
|
|
||||||
|
if (name === 'write') {
|
||||||
|
eventEmitter.emit('writePacket', ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectMethodsToWorker(['write', 'registerChannel', 'writeChannel'])
|
||||||
|
|
||||||
|
if (authData) {
|
||||||
|
void authFlowMainThread(worker, authData, connectOptions, (onJoin) => {
|
||||||
|
eventEmitter.on('login', onJoin)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventEmitter
|
||||||
|
// return new Proxy(eventEmitter, {
|
||||||
|
// get (target, prop) {
|
||||||
|
// if (!(prop in target)) {
|
||||||
|
// // console.warn(`Accessing non-existent property "${String(prop)}" on event emitter`)
|
||||||
|
// }
|
||||||
|
// const value = target[prop]
|
||||||
|
// return typeof value === 'function' ? value.bind(target) : value
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
return createMinecraftProtocolClient
|
||||||
|
}
|
||||||
177
src/protocolWorker/protocolWorker.ts
Normal file
177
src/protocolWorker/protocolWorker.ts
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
|
import './protocolWorkerGlobals'
|
||||||
|
import * as net from 'net'
|
||||||
|
import { Client, createClient } from 'minecraft-protocol'
|
||||||
|
import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth'
|
||||||
|
import { validatePacket } from '../mineflayer/minecraft-protocol-extra'
|
||||||
|
|
||||||
|
// This is a Web Worker for handling protocol-related tasks
|
||||||
|
// Respond to messages from the main thread
|
||||||
|
self.onmessage = (e) => {
|
||||||
|
const handler = handlers[e.data.type]
|
||||||
|
if (handler) {
|
||||||
|
handler(e.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const REDIRECT_EVENTS = ['connection', 'listening', 'playerJoin', 'end']
|
||||||
|
const ENABLE_TRANSFER = false
|
||||||
|
|
||||||
|
const emitEvent = (event: string, ...args: any[]) => {
|
||||||
|
const transfer = ENABLE_TRANSFER ? args.filter(arg => arg instanceof ArrayBuffer || arg instanceof MessagePort || arg instanceof ImageBitmap || arg instanceof OffscreenCanvas || arg instanceof ImageData) : []
|
||||||
|
self.postMessage({ type: 'event', event, args }, transfer as any)
|
||||||
|
}
|
||||||
|
let client: Client
|
||||||
|
const registeredChannels = [] as string[]
|
||||||
|
let skipWriteLog = false
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
setProxy (data: { hostname: string, port: number }) {
|
||||||
|
console.log('[protocolWorker] using proxy', data)
|
||||||
|
net['setProxy']({ hostname: data.hostname, port: data.port })
|
||||||
|
},
|
||||||
|
async init ({ options, noPacketsValidation, useAuthFlow }: { options: any, noPacketsValidation: boolean, useAuthFlow: boolean }) {
|
||||||
|
if (client) throw new Error('Client already initialized')
|
||||||
|
await globalThis._LOAD_MC_DATA()
|
||||||
|
if (useAuthFlow) {
|
||||||
|
options.auth = authFlowWorkerThread
|
||||||
|
}
|
||||||
|
client = createClient(options)
|
||||||
|
|
||||||
|
for (const event of REDIRECT_EVENTS) {
|
||||||
|
client.on(event, () => {
|
||||||
|
emitEvent(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldWrite = client.write
|
||||||
|
client.write = (...args) => {
|
||||||
|
if (!skipWriteLog) {
|
||||||
|
emitEvent('writePacket', ...args)
|
||||||
|
}
|
||||||
|
return oldWrite.apply(client, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.on('packet', (data, packetMeta, buffer, fullBuffer) => {
|
||||||
|
if (window.stopPacketsProcessing) return
|
||||||
|
if (!noPacketsValidation) {
|
||||||
|
validatePacket(packetMeta.name, data, fullBuffer, true)
|
||||||
|
}
|
||||||
|
emitEvent('packet', data, packetMeta, {}, { byteLength: fullBuffer.byteLength })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
call (data: { name: string, args: any[] }) {
|
||||||
|
if (data.name === 'write') {
|
||||||
|
skipWriteLog = true
|
||||||
|
}
|
||||||
|
client[data.name].bind(client)(...data.args)
|
||||||
|
|
||||||
|
if (data.name === 'registerChannel' && !registeredChannels.includes(data.args[0])) {
|
||||||
|
client.on(data.args[0], (...args: any[]) => {
|
||||||
|
emitEvent(data.args[0], ...args)
|
||||||
|
})
|
||||||
|
registeredChannels.push(data.args[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const authFlowWorkerThread = async (client, options) => {
|
||||||
|
self.postMessage({
|
||||||
|
type: 'authFlow',
|
||||||
|
version: client.version,
|
||||||
|
username: client.username
|
||||||
|
})
|
||||||
|
options.onMsaCode = (data) => {
|
||||||
|
self.postMessage({
|
||||||
|
type: 'msaCode',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
client.authflow = {
|
||||||
|
async getMinecraftJavaToken () {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
self.addEventListener('message', async (e) => {
|
||||||
|
if (e.data.type === 'authflowResult') {
|
||||||
|
const restoredData = await restoreData(e.data.data)
|
||||||
|
if (restoredData?.certificates?.profileKeys?.privatePEM) {
|
||||||
|
restoredData.certificates.profileKeys.private = restoredData.certificates.profileKeys.privatePEM
|
||||||
|
}
|
||||||
|
resolve(restoredData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.race([
|
||||||
|
protocolMicrosoftAuth.authenticate(client, options),
|
||||||
|
// new Promise((_r, reject) => {
|
||||||
|
// signInMessageState.abortController.signal.addEventListener('abort', () => {
|
||||||
|
// reject(new UserError('Aborted by user'))
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore dates from strings
|
||||||
|
const restoreData = async (json) => {
|
||||||
|
const promises = [] as Array<Promise<void>>
|
||||||
|
if (typeof json === 'object' && json) {
|
||||||
|
for (const [key, value] of Object.entries(json)) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
promises.push(tryRestorePublicKey(value, key, json))
|
||||||
|
if (value.endsWith('Z')) {
|
||||||
|
const date = new Date(value)
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
json[key] = date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await restoreData(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises)
|
||||||
|
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
const tryRestorePublicKey = async (value: string, name: string, parent: { [x: string]: any }) => {
|
||||||
|
value = value.trim()
|
||||||
|
if (!name.endsWith('PEM') || !value.startsWith('-----BEGIN RSA PUBLIC KEY-----') || !value.endsWith('-----END RSA PUBLIC KEY-----')) return
|
||||||
|
const der = pemToArrayBuffer(value)
|
||||||
|
const key = await window.crypto.subtle.importKey(
|
||||||
|
'spki', // Specify that the data is in SPKI format
|
||||||
|
der,
|
||||||
|
{
|
||||||
|
name: 'RSA-OAEP',
|
||||||
|
hash: { name: 'SHA-256' }
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
['encrypt'] // Specify key usages
|
||||||
|
)
|
||||||
|
const originalName = name.replace('PEM', '')
|
||||||
|
const exported = await window.crypto.subtle.exportKey('spki', key)
|
||||||
|
const exportedBuffer = new Uint8Array(exported)
|
||||||
|
parent[originalName] = {
|
||||||
|
export () {
|
||||||
|
return exportedBuffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pemToArrayBuffer (pem) {
|
||||||
|
// Fetch the part of the PEM string between header and footer
|
||||||
|
const pemHeader = '-----BEGIN RSA PUBLIC KEY-----'
|
||||||
|
const pemFooter = '-----END RSA PUBLIC KEY-----'
|
||||||
|
const pemContents = pem.slice(pemHeader.length, pem.length - pemFooter.length).trim()
|
||||||
|
const binaryDerString = atob(pemContents.replaceAll(/\s/g, ''))
|
||||||
|
const binaryDer = new Uint8Array(binaryDerString.length)
|
||||||
|
for (let i = 0; i < binaryDerString.length; i++) {
|
||||||
|
binaryDer[i] = binaryDerString.codePointAt(i)!
|
||||||
|
}
|
||||||
|
return binaryDer.buffer
|
||||||
|
}
|
||||||
4
src/protocolWorker/protocolWorkerGlobals.ts
Normal file
4
src/protocolWorker/protocolWorkerGlobals.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
|
globalThis.window = self
|
||||||
|
//@ts-expect-error
|
||||||
|
process.versions = { node: '' }
|
||||||
|
|
@ -4,7 +4,7 @@ import { activeModalStack, activeModalStacks, hideModal, insertActiveModalStack,
|
||||||
import { guessProblem } from '../errorLoadingScreenHelpers'
|
import { guessProblem } from '../errorLoadingScreenHelpers'
|
||||||
import type { ConnectOptions } from '../connect'
|
import type { ConnectOptions } from '../connect'
|
||||||
import { downloadPacketsReplay, packetsRecordingState, replayLogger } from '../packetsReplay/packetsReplayLegacy'
|
import { downloadPacketsReplay, packetsRecordingState, replayLogger } from '../packetsReplay/packetsReplayLegacy'
|
||||||
import { getProxyDetails } from '../microsoftAuthflow'
|
import { getProxyDetails } from '../protocolWorker/microsoftAuthflow'
|
||||||
import { downloadAutoCapturedPackets, getLastAutoCapturedPackets } from '../mineflayer/plugins/packetsRecording'
|
import { downloadAutoCapturedPackets, getLastAutoCapturedPackets } from '../mineflayer/plugins/packetsRecording'
|
||||||
import { appQueryParams } from '../appParams'
|
import { appQueryParams } from '../appParams'
|
||||||
import AppStatus from './AppStatus'
|
import AppStatus from './AppStatus'
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
|
||||||
lockedEditing={!!customServersList}
|
lockedEditing={!!customServersList}
|
||||||
setQuickConnectIp={setQuickConnectIp}
|
setQuickConnectIp={setQuickConnectIp}
|
||||||
onProfileClick={async () => {
|
onProfileClick={async () => {
|
||||||
const username = await showOptionsModal('Select authenticated account to remove', authenticatedAccounts.map(a => a.username))
|
const username = await showOptionsModal('Select authenticated account to remove', authenticatedAccounts.map(a => a.username ?? '??'))
|
||||||
if (!username) return
|
if (!username) return
|
||||||
appStorage.authenticatedAccounts = authenticatedAccounts.filter(a => a.username !== username)
|
appStorage.authenticatedAccounts = authenticatedAccounts.filter(a => a.username !== username)
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
45
src/react/components/LibraryVersions.tsx
Normal file
45
src/react/components/LibraryVersions.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react'
|
||||||
|
import physicsUtilPkg from '@nxg-org/mineflayer-physics-util/package.json'
|
||||||
|
import mineflayerPkg from 'mineflayer/package.json'
|
||||||
|
import mcProtocolPkg from 'minecraft-protocol/package.json'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import packageJson from '../../../package.json'
|
||||||
|
import { miscUiState } from '../../globalState'
|
||||||
|
|
||||||
|
const LibraryVersions: React.FC = () => {
|
||||||
|
const versions = {
|
||||||
|
'@nxg-org/mineflayer-physics-util': physicsUtilPkg.version,
|
||||||
|
'mineflayer': packageJson.devDependencies['mineflayer'],
|
||||||
|
'minecraft-protocol': mcProtocolPkg.version
|
||||||
|
}
|
||||||
|
|
||||||
|
const { gameLoaded } = useSnapshot(miscUiState)
|
||||||
|
|
||||||
|
if (!gameLoaded) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
pointerEvents: 'none',
|
||||||
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
top: '300px',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||||
|
color: 'white',
|
||||||
|
padding: '10px',
|
||||||
|
borderRadius: '0 5px 5px 0',
|
||||||
|
fontSize: '8px',
|
||||||
|
zIndex: 1000
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>Library Versions:</div>
|
||||||
|
{Object.entries(versions).map(([lib, version]) => (
|
||||||
|
<div key={lib} style={{ marginTop: '5px' }}>
|
||||||
|
{lib}: {version}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LibraryVersions
|
||||||
|
|
@ -46,6 +46,7 @@ import BookProvider from './react/BookProvider'
|
||||||
import { options } from './optionsStorage'
|
import { options } from './optionsStorage'
|
||||||
import BossBarOverlayProvider from './react/BossBarOverlayProvider'
|
import BossBarOverlayProvider from './react/BossBarOverlayProvider'
|
||||||
import DebugEdges from './react/DebugEdges'
|
import DebugEdges from './react/DebugEdges'
|
||||||
|
import LibraryVersions from './react/components/LibraryVersions'
|
||||||
import GameInteractionOverlay from './react/GameInteractionOverlay'
|
import GameInteractionOverlay from './react/GameInteractionOverlay'
|
||||||
import MineflayerPluginHud from './react/MineflayerPluginHud'
|
import MineflayerPluginHud from './react/MineflayerPluginHud'
|
||||||
import MineflayerPluginConsole from './react/MineflayerPluginConsole'
|
import MineflayerPluginConsole from './react/MineflayerPluginConsole'
|
||||||
|
|
@ -212,6 +213,7 @@ const App = () => {
|
||||||
</RobustPortal>
|
</RobustPortal>
|
||||||
<EnterFullscreenButton />
|
<EnterFullscreenButton />
|
||||||
<InGameUi />
|
<InGameUi />
|
||||||
|
<LibraryVersions />
|
||||||
<RobustPortal to={document.querySelector('#ui-root')}>
|
<RobustPortal to={document.querySelector('#ui-root')}>
|
||||||
<AllWidgets />
|
<AllWidgets />
|
||||||
<SingleplayerProvider />
|
<SingleplayerProvider />
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { versionToNumber } from 'renderer/viewer/common/utils'
|
import { toMajorVersion } from 'renderer/viewer/lib/simpleUtils'
|
||||||
|
import { versionToNumber } from 'mc-assets/dist/utils'
|
||||||
import { restoreMinecraftData } from '../optimizeJson'
|
import { restoreMinecraftData } from '../optimizeJson'
|
||||||
// import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
|
|
||||||
import { toMajorVersion } from '../utils'
|
|
||||||
import { importLargeData } from '../../generated/large-data-aliases'
|
import { importLargeData } from '../../generated/large-data-aliases'
|
||||||
|
// import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
|
||||||
|
|
||||||
const customResolver = () => {
|
const customResolver = () => {
|
||||||
const resolver = Promise.withResolvers()
|
const resolver = Promise.withResolvers()
|
||||||
|
|
|
||||||
|
|
@ -126,12 +126,6 @@ export const isMajorVersionGreater = (ver1: string, ver2: string) => {
|
||||||
return +a1 > +a2 || (+a1 === +a2 && +b1 > +b2)
|
return +a1 > +a2 || (+a1 === +a2 && +b1 > +b2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// doesn't support snapshots
|
|
||||||
export const toMajorVersion = version => {
|
|
||||||
const [a, b] = (String(version)).split('.')
|
|
||||||
return `${a}.${b}`
|
|
||||||
}
|
|
||||||
|
|
||||||
let prevRenderDistance = options.renderDistance
|
let prevRenderDistance = options.renderDistance
|
||||||
export const setRenderDistance = () => {
|
export const setRenderDistance = () => {
|
||||||
assertDefined(worldView)
|
assertDefined(worldView)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue