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/switch-case-braces": "off",
|
||||
"@typescript-eslint/consistent-type-definitions": "off",
|
||||
"unicorn/relative-url-style": "off",
|
||||
"unicorn/explicit-length-check": "off",
|
||||
"unicorn/prefer-dom-node-append": "off",
|
||||
"typescript-eslint/no-confusing-void-expression": "off",
|
||||
|
|
|
|||
15
config.json
15
config.json
|
|
@ -10,16 +10,15 @@
|
|||
"ip": "wss://mcraft.ryzyn.xyz",
|
||||
"version": "1.19.4"
|
||||
},
|
||||
{
|
||||
"ip": "grim.mcraft.fun",
|
||||
"version": "1.19.4"
|
||||
},
|
||||
{
|
||||
"ip": "play.hypixel.net"
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -118,11 +118,11 @@
|
|||
"workbox-build": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rsbuild/core": "^1.0.1-beta.9",
|
||||
"@rsbuild/plugin-node-polyfill": "^1.0.3",
|
||||
"@rsbuild/plugin-react": "^1.0.1-beta.9",
|
||||
"@rsbuild/plugin-type-check": "^1.0.1-beta.9",
|
||||
"@rsbuild/plugin-typed-css-modules": "^1.0.1",
|
||||
"@rsbuild/core": "1.0.1-beta.9",
|
||||
"@rsbuild/plugin-node-polyfill": "1.0.3",
|
||||
"@rsbuild/plugin-react": "1.0.1-beta.9",
|
||||
"@rsbuild/plugin-type-check": "1.0.1-beta.9",
|
||||
"@rsbuild/plugin-typed-css-modules": "1.0.1",
|
||||
"@storybook/addon-essentials": "^7.4.6",
|
||||
"@storybook/addon-links": "^7.4.6",
|
||||
"@storybook/blocks": "^7.4.6",
|
||||
|
|
@ -153,7 +153,7 @@
|
|||
"mc-assets": "^0.2.45",
|
||||
"mineflayer-mouse": "^0.1.7",
|
||||
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
|
||||
"mineflayer": "github:zardoy/mineflayer",
|
||||
"mineflayer": "github:GenerelSchwerz/mineflayer",
|
||||
"mineflayer-pathfinder": "^2.4.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"os-browserify": "^0.3.0",
|
||||
|
|
@ -194,9 +194,9 @@
|
|||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@nxg-org/mineflayer-physics-util": "latest",
|
||||
"buffer": "^6.0.3",
|
||||
"vec3": "0.1.10",
|
||||
"@nxg-org/mineflayer-physics-util": "1.5.8",
|
||||
"three": "0.154.0",
|
||||
"diamond-square": "github:zardoy/diamond-square",
|
||||
"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
|
||||
import GUI from 'lil-gui'
|
||||
import _ from 'lodash'
|
||||
import { toMajorVersion } from '../../src/utils'
|
||||
import { WorldDataEmitter } from '../viewer'
|
||||
import { Viewer } from '../viewer/lib/viewer'
|
||||
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)
|
||||
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 { generateSpiralMatrix } from 'flying-squid/dist/utils'
|
||||
import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs'
|
||||
import { toMajorVersion } from '../../../src/utils'
|
||||
import { ResourcesManager } from '../../../src/resourcesManager'
|
||||
import { DisplayWorldOptions, RendererReactiveState } from '../../../src/appViewer'
|
||||
import { SoundSystem } from '../three/threeJsSound'
|
||||
import { buildCleanupDecorator } from './cleanupDecorator'
|
||||
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 { WorldDataEmitter } from './worldDataEmitter'
|
||||
import { IPlayerState } from './basePlayerState'
|
||||
|
|
|
|||
121
src/controls.ts
121
src/controls.ts
|
|
@ -712,142 +712,32 @@ document.addEventListener('visibilitychange', (e) => {
|
|||
}
|
||||
})
|
||||
|
||||
// #region creative fly
|
||||
// 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 isFlying = () => (bot.entity as any).flying
|
||||
|
||||
const startFlying = (sendAbilities = true) => {
|
||||
bot.entity['creativeFly'] = true
|
||||
if (sendAbilities) {
|
||||
bot._client.write('abilities', {
|
||||
flags: 2,
|
||||
})
|
||||
}
|
||||
// window.flyingSpeed will be removed
|
||||
bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 // todo use abilities
|
||||
bot.entity.velocity = new Vec3(0, 0, 0)
|
||||
bot.creative.startFlying()
|
||||
startFlyLoop()
|
||||
(bot.entity as any).flying = true
|
||||
}
|
||||
|
||||
const endFlying = (sendAbilities = true) => {
|
||||
bot.entity['creativeFly'] = false
|
||||
if (bot.physics.gravity !== 0) return
|
||||
if (!isFlying()) return
|
||||
if (sendAbilities) {
|
||||
bot._client.write('abilities', {
|
||||
flags: 0,
|
||||
})
|
||||
}
|
||||
Object.assign(flyingPressedKeys, {
|
||||
up: false,
|
||||
down: false
|
||||
})
|
||||
currentFlyVector.set(0, 0, 0)
|
||||
bot.physics['airborneAcceleration'] = standardAirborneAcceleration
|
||||
bot.creative.stopFlying()
|
||||
endFlyLoop?.()
|
||||
(bot.entity as any).flying = false
|
||||
}
|
||||
|
||||
let allowFlying = false
|
||||
|
||||
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) => {
|
||||
// if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return
|
||||
if (!allowFlying) return
|
||||
if (bot.setControlState !== patchedSetControlState) {
|
||||
originalSetControlState = bot.setControlState
|
||||
bot.setControlState = patchedSetControlState
|
||||
}
|
||||
if (!bot.entity.canFly) return
|
||||
|
||||
if (newState) {
|
||||
startFlying(sendAbilities)
|
||||
|
|
@ -856,7 +746,6 @@ const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => {
|
|||
}
|
||||
gameAdditionalState.isFlying = isFlying()
|
||||
}
|
||||
// #endregion
|
||||
|
||||
const selectItem = async () => {
|
||||
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 { onGameLoad } from './inventoryWindows'
|
||||
import initCollisionShapes from './getCollisionInteractionShapes'
|
||||
import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth'
|
||||
import microsoftAuthflow from './microsoftAuthflow'
|
||||
import { Duplex } from 'stream'
|
||||
|
||||
import './scaleInterface'
|
||||
|
|
@ -75,7 +73,7 @@ import { saveToBrowserMemory } from './react/PauseScreen'
|
|||
import './devReload'
|
||||
import './water'
|
||||
import { ConnectOptions, loadMinecraftData, getVersionAutoSelect, downloadOtherGameData, downloadAllMinecraftData } from './connect'
|
||||
import { ref, subscribe } from 'valtio'
|
||||
import { subscribe } from 'valtio'
|
||||
import { signInMessageState } from './react/SignInMessageProvider'
|
||||
import { updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage'
|
||||
import packetsPatcher from './mineflayer/plugins/packetsPatcher'
|
||||
|
|
@ -97,6 +95,7 @@ import { createConsoleLogProgressReporter, createFullScreenProgressReporter, Pro
|
|||
import { appViewer } from './appViewer'
|
||||
import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend'
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
import { getProtocolClientGetter } from './protocolWorker/protocolMain'
|
||||
|
||||
window.debug = debug
|
||||
window.beforeRenderFrame = []
|
||||
|
|
@ -190,8 +189,8 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
|
||||
const { renderDistance: renderDistanceSingleplayer, multiplayerRenderDistance } = options
|
||||
|
||||
const parsedServer = parseServerAddress(connectOptions.server)
|
||||
const server = { host: parsedServer.host, port: parsedServer.port }
|
||||
const serverParsed = parseServerAddress(connectOptions.server)
|
||||
const server = { host: serverParsed.host, port: serverParsed.port }
|
||||
if (connectOptions.proxy?.startsWith(':')) {
|
||||
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:'
|
||||
connectOptions.proxy = `${connectOptions.proxy}:${https ? 443 : 80}`
|
||||
}
|
||||
const parsedProxy = parseServerAddress(connectOptions.proxy, false)
|
||||
const proxy = { host: parsedProxy.host, port: parsedProxy.port }
|
||||
const proxyParsed = parseServerAddress(connectOptions.proxy, false)
|
||||
const proxy = { host: proxyParsed.host, port: proxyParsed.port }
|
||||
let { username } = connectOptions
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -244,7 +243,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
})
|
||||
}
|
||||
}
|
||||
let lastPacket = undefined as string | undefined
|
||||
const lastPacket = undefined as string | undefined
|
||||
const onPossibleErrorDisconnect = () => {
|
||||
if (lastPacket && bot?._client && bot._client.state !== states.PLAY) {
|
||||
appStatusState.descriptionHint = `Last Server Packet: ${lastPacket}`
|
||||
|
|
@ -285,13 +284,12 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
|
||||
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}`)
|
||||
net['setProxy']({ hostname: proxy.host, port: proxy.port })
|
||||
}
|
||||
|
||||
const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance
|
||||
let updateDataAfterJoin = () => { }
|
||||
let localServer
|
||||
let localReplaySession: ReturnType<typeof startLocalReplayServer> | undefined
|
||||
try {
|
||||
|
|
@ -411,23 +409,11 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
}
|
||||
setLoadingScreenStatus(initialLoadingText)
|
||||
|
||||
if (parsedServer.isWebSocket) {
|
||||
if (serverParsed.isWebSocket) {
|
||||
clientDataStream = (await getWebsocketStream(server.host)).mineflayerStream
|
||||
}
|
||||
|
||||
let newTokensCacheResult = null as any
|
||||
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) {
|
||||
clientDataStream = await connectToPeer(connectOptions.peerId!, connectOptions.peerOptions)
|
||||
|
|
@ -458,9 +444,13 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
await downloadMcData(finalVersion)
|
||||
}
|
||||
|
||||
const brand = clientDataStream ? 'minecraft-web-client' : undefined
|
||||
const createClient = await getProtocolClientGetter(proxy, connectOptions, serverParsed.serverIpFull)
|
||||
|
||||
bot = mineflayer.createBot({
|
||||
host: server.host,
|
||||
port: server.port ? +server.port : undefined,
|
||||
brand,
|
||||
version: finalVersion || false,
|
||||
...clientDataStream ? {
|
||||
stream: clientDataStream as any,
|
||||
|
|
@ -477,53 +467,13 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
connect () { },
|
||||
Client: CustomChannelClient as any,
|
||||
} : {},
|
||||
onMsaCode (data) {
|
||||
signInMessageState.code = data.user_code
|
||||
signInMessageState.link = data.verification_uri
|
||||
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 = ''
|
||||
get client () {
|
||||
if (clientDataStream || singleplayer || p2pMultiplayer || localReplaySession || connectOptions.viewerWsConnect) {
|
||||
return undefined
|
||||
}
|
||||
} : undefined,
|
||||
return createClient.call(this)
|
||||
},
|
||||
// auth: connectOptions.authenticatedAccount ? : undefined,
|
||||
username,
|
||||
viewDistance: renderDistance,
|
||||
checkTimeoutInterval: 240 * 1000,
|
||||
|
|
@ -579,16 +529,16 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
})
|
||||
}
|
||||
// socket setup actually can be delayed because of dns lookup
|
||||
if (bot._client.socket) {
|
||||
setupConnectHandlers()
|
||||
} else {
|
||||
const originalSetSocket = bot._client.setSocket.bind(bot._client)
|
||||
bot._client.setSocket = (socket) => {
|
||||
if (!bot) return
|
||||
originalSetSocket(socket)
|
||||
setupConnectHandlers()
|
||||
}
|
||||
}
|
||||
// if (bot._client.socket) {
|
||||
// setupConnectHandlers()
|
||||
// } else {
|
||||
// const originalSetSocket = bot._client.setSocket.bind(bot._client)
|
||||
// bot._client.setSocket = (socket) => {
|
||||
// if (!bot) return
|
||||
// originalSetSocket(socket)
|
||||
// setupConnectHandlers()
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
} 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)
|
||||
|
||||
const packetBeforePlay = (_, __, ___, fullBuffer) => {
|
||||
lastPacket = fullBuffer.toString()
|
||||
// lastPacket = fullBuffer.toString()
|
||||
}
|
||||
bot._client.on('packet', packetBeforePlay as any)
|
||||
const playStateSwitch = (newState) => {
|
||||
|
|
@ -675,6 +625,10 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
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
|
||||
// don't use spawn event, player can be dead
|
||||
bot.once(spawnEarlier ? 'forcedMove' : 'health', async () => {
|
||||
|
|
@ -703,7 +657,6 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
localStorage.removeItem('lastConnectOptions')
|
||||
}
|
||||
connectOptions.onSuccessfulPlay?.()
|
||||
updateDataAfterJoin()
|
||||
if (connectOptions.autoLoginPassword) {
|
||||
bot.chat(`/login ${connectOptions.autoLoginPassword}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
||||
|
||||
customEvents.on('hurtAnimation', () => {
|
||||
cameraShake.shakeFromDamage()
|
||||
})
|
||||
|
||||
customEvents.on('mineflayerBotCreated', () => {
|
||||
customEvents.on('hurtAnimation', (yaw) => {
|
||||
getThreeJsRendererMethods()?.shakeFromDamage()
|
||||
|
|
|
|||
|
|
@ -7,16 +7,10 @@ import { getWebsocketStream } from './websocket-core'
|
|||
|
||||
let lastPacketTime = 0
|
||||
customEvents.on('mineflayerBotCreated', () => {
|
||||
// todo move more code here
|
||||
if (!appQueryParams.noPacketsValidation) {
|
||||
(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)
|
||||
})
|
||||
}
|
||||
(bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => {
|
||||
lastPacketTime = performance.now()
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
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) => {
|
||||
if (!proxyBaseUrl.startsWith('http')) proxyBaseUrl = `${isPageSecure() ? 'https' : 'http'}://${proxyBaseUrl}`
|
||||
const url = `${proxyBaseUrl}/api/vm/net/connect`
|
||||
|
|
@ -10,13 +17,14 @@ export const getProxyDetails = async (proxyBaseUrl: string) => {
|
|||
return result
|
||||
}
|
||||
|
||||
export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { }, setCacheResult, connectingServer }) => {
|
||||
export const getAuthData = async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { }, connectingServer }) => {
|
||||
let onMsaCodeCallback
|
||||
let connectingVersion = ''
|
||||
// const authEndpoint = 'http://localhost:3000/'
|
||||
// const sessionEndpoint = 'http://localhost:3000/session'
|
||||
let authEndpoint: URL | undefined
|
||||
let sessionEndpoint: URL | undefined
|
||||
let newTokensCacheResult = null as any
|
||||
const result = await getProxyDetails(proxyBaseUrl)
|
||||
|
||||
try {
|
||||
|
|
@ -32,7 +40,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
|
|||
async getMinecraftJavaToken () {
|
||||
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!')
|
||||
let result = null
|
||||
let result = null as any
|
||||
await fetch(authEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
|
@ -73,7 +81,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
|
|||
}
|
||||
if (json.error) throw new Error(json.error)
|
||||
if (json.token) result = json
|
||||
if (json.newCache) setCacheResult(json.newCache)
|
||||
if (json.newCache) newTokensCacheResult = json.newCache
|
||||
}
|
||||
|
||||
const strings = decoder.decode(value)
|
||||
|
|
@ -86,11 +94,7 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
|
|||
}
|
||||
return reader.read().then(processText)
|
||||
})
|
||||
const restoredData = await restoreData(result)
|
||||
if (restoredData?.certificates?.profileKeys?.privatePEM) {
|
||||
restoredData.certificates.profileKeys.private = restoredData.certificates.profileKeys.privatePEM
|
||||
}
|
||||
return restoredData
|
||||
return result
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
|
@ -101,77 +105,68 @@ export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => {
|
|||
},
|
||||
setConnectingVersion (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) {
|
||||
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 defaultBase = isPageSecure() ? 'https' : 'http'
|
||||
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 type { ConnectOptions } from '../connect'
|
||||
import { downloadPacketsReplay, packetsRecordingState, replayLogger } from '../packetsReplay/packetsReplayLegacy'
|
||||
import { getProxyDetails } from '../microsoftAuthflow'
|
||||
import { getProxyDetails } from '../protocolWorker/microsoftAuthflow'
|
||||
import { downloadAutoCapturedPackets, getLastAutoCapturedPackets } from '../mineflayer/plugins/packetsRecording'
|
||||
import { appQueryParams } from '../appParams'
|
||||
import AppStatus from './AppStatus'
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
|
|||
lockedEditing={!!customServersList}
|
||||
setQuickConnectIp={setQuickConnectIp}
|
||||
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
|
||||
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 BossBarOverlayProvider from './react/BossBarOverlayProvider'
|
||||
import DebugEdges from './react/DebugEdges'
|
||||
import LibraryVersions from './react/components/LibraryVersions'
|
||||
import GameInteractionOverlay from './react/GameInteractionOverlay'
|
||||
import MineflayerPluginHud from './react/MineflayerPluginHud'
|
||||
import MineflayerPluginConsole from './react/MineflayerPluginConsole'
|
||||
|
|
@ -212,6 +213,7 @@ const App = () => {
|
|||
</RobustPortal>
|
||||
<EnterFullscreenButton />
|
||||
<InGameUi />
|
||||
<LibraryVersions />
|
||||
<RobustPortal to={document.querySelector('#ui-root')}>
|
||||
<AllWidgets />
|
||||
<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 minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
|
||||
import { toMajorVersion } from '../utils'
|
||||
import { importLargeData } from '../../generated/large-data-aliases'
|
||||
// import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
|
||||
|
||||
const customResolver = () => {
|
||||
const resolver = Promise.withResolvers()
|
||||
|
|
|
|||
|
|
@ -126,12 +126,6 @@ export const isMajorVersionGreater = (ver1: string, ver2: string) => {
|
|||
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
|
||||
export const setRenderDistance = () => {
|
||||
assertDefined(worldView)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue