Skip to content
On this page

Driving a real client (BeDD)

bedboo normally is the client: it logs in to Bedrock Dedicated Server over RakNet or NetherNet and sends every packet itself. With transport: 'bedd' it drives a real Minecraft Bedrock 1.26.51.01 client instead, hosted by Be.D.D. (Bedrock Deep Dive, a LeviLamina client mod). The same script then runs against the game window on your desk: you watch the local player walk, look, dig, place and chat as the script says.

js
const bedboo = require('bedboo')
const bot = bedboo.createBot({ transport: 'bedd', bedd: { pipe: '\\\\.\\pipe\\BeDD' } })
bot.once('spawn', () => bot.chat('hello from bedboo'))

Choosing between the modes is only transport; direct BDS mode (raknet, nethernet) is unchanged. The differences of this mode, entry by entry: BeDD mode.

Setup

Windows only (BeDD's pipes are Windows named pipes); Node 24 or later.

  1. Install BeDD into a Minecraft Bedrock 1.26.51.01 instance (see the BeDD docs; clone-instance.ps1 makes a separate instance, Start-BeDDGame.ps1 -Instance <name> starts it).

  2. Turn the bedboo features on in the instance's mods\BeDD\config\config.json. Every one of them is off by default:

    keydefaultwhat it enablesbedboo needs it for
    pipeNameBeDDthe control pipe \\.\pipe\<pipeName>; the tap pipe is \\.\pipe\<pipeName>-tapbedd.pipe (the tap name is derived)
    tapfalsethe tap pipe: every clientbound packet, an echo of every serverbound packet, markers, a join journaleverything (without it bedboo cannot attach)
    tapLoopbacktruetapping singleplayer / LAN-host worlds (loopback link, re-serialized packets)singleplayer
    tapJournalMiB128size cap of the join journal replayed to a late attachattaching mid-session (complete:false past the cap)
    gameHooksfalsethe world, look and input ops (hooks in the game's own input pipeline)precise movement and looking (without it: virtual keys and mouse)
    allowSendfalsethe send op for the allowlisted packet ids (sendAllow, default [9, 77] = text, command_request)bot.chat, whispers, commands
    allowStackfalsethe stack op: item_stack_request submitted through the client's own ItemStackNetManagerinventory: clickWindow, transfer, craft, deposit/withdraw, equip from the main inventory, creative
    pipeInstances4how many control-pipe clients may be connected at oncebedboo next to BeDD.psm1 and other harnesses

    A config for bedboo:

    json
    { "pipeName": "BeDD-1.26.51.01-hi20", "tap": true, "tapLoopback": true, "gameHooks": true,
      "allowSend": true, "allowStack": true, "pipeInstances": 4, "loopbackOnly": true }

    loopbackOnly (BeDD's own key) keeps the client on servers of the same PC (127.0.0.1). The client joins BDS 1.26.51 over NetherNet, so start the server with transport=nethernet; NetherNet joins go through Minecraft's online signalling service, so no other Minecraft game on the PC may start while the client joins and plays (a later sign-in knocks the session out: "Multiplayer Connection Failed … NetherNet").

  3. Start the game and get the player into a world (a server, a LAN world or singleplayer): bedboo attaches to a client that is already playing, it does not log in.

  4. createBot({ transport: 'bedd', bedd: { pipe: '\\\\.\\pipe\\BeDD-1.26.51.01-hi20' } }). spawn fires once the join journal has been replayed; from then on bot.* is the client's state.

The bedd_follow.js example makes the client follow the nearest other player:

sh
node examples/bedd_follow.js --pipe \\.\pipe\BeDD-1.26.51.01-hi20

How it works

The real client stays authoritative for everything it sends: login, resource packs, the loading screen, chunk requests, player_auth_input every tick (physics included), block actions, inventory transactions and item stack requests. bedboo never forges those packets. Its whole stack still runs, and what it would have sent becomes an intent for the client:

text
script → mineflayer API → plugins → _client.queue → router ──(JSON ops)──────→ \\.\pipe\<pipeName>     → the game's input pipeline
bot.* state ← plugins ← decoder ← tap reader ←──(binary frames)──── \\.\pipe\<pipeName>-tap ← every packet the client gets / sends
  • State in: the tap pipe carries every clientbound packet the client receives, an echo of every serverbound packet it sends, and JSON markers (HELLO, LINK_UP/LINK_DOWN, REPLAY_BEGIN/REPLAY_END, DROPPED). bedboo decodes them with bedrock-protocol exactly as in direct mode, so bot.entities, the world, inventory, chat and every event come from the real session. A join journal (since start_game, compacted: the latest chunk per position, the latest attributes per entity, removed entities pruned) is replayed to every new tap client, so bedboo can attach mid-session. If bedboo falls more than 32 MiB behind, BeDD sends DROPPED and closes; bedboo reconnects and gets a full replay.
  • Self state: bot.entity position, velocity, yaw, pitch and onGround follow the echoed player_auth_input (eye − 1.62). physicsTick and move fire once per client tick; bedboo's own physics engine does not step.
  • Intents out: a router diffs what bedboo would have sent and calls BeDD's control ops: input (move vector, jump, sneak, sprint, with a dead-man TTL), look (absolute yaw/pitch), slot, button (attack/use/pick, released on block change for digging), action (drop, inventory, …), send (allowlisted text/command_request bytes) and stack (item_stack_request through the client's ItemStackNetManager). Input is applied only while the game has the mouse and shows no menu.
  • Negotiation: at attach, bedboo pings the control pipe and probes each op with no arguments: unknown op → absent, code:'disabled' → present but turned off in config.json, ok → present. bot.bedrock.bedd.caps holds the result and bot.bedrock.bedd.supports(name) answers per mineflayer API.

Authority table

classpacketshandling
clientOwnedlogin, client_to_server_handshake, request_network_settings, resource_pack_client_response, client_cache_status, client_cache_blob_status, request_chunk_radius, subchunk_request, serverbound_loading_screen, set_local_player_as_initialized, network_stack_latency, tick_sync, player_skin, emote_list, respawn, player_action (dimension_change_ack / start_item_use_on / stop_item_use_on), animate, interact mouse_over_entitydropped by the router (the client sends its own); counted
translatedplayer_auth_input (move_vector and flags → input; yaw/pitch → look; block_actions start_break / abort_break / stop_break → button attack), mob_equipment (→ slot), inventory_transaction (item_use click_block / click_air, item_release, item_use_on_entity → look + button), interact open_inventory (→ action inventory), container_close (→ ESC), item_stack_request (→ stack)translated into control ops
sharedtext, command_requestsent as bytes through send (the command uuid is kept, so command_output correlates); fallback: T / SLASH key, text, ENTER
unsupportedbook_edit, block_pick_request, crafting_event, set_default_game_type, everything elseBeddUnsupportedError (code: 'ERR_BEDD_UNSUPPORTED')

Coverage

"Caps" names the BeDD ops the API needs; without them bot.bedrock.bedd.supports(name) is false and the call throws or rejects with ERR_BEDD_UNSUPPORTED (or uses the stated fallback).

APIworkscaps (fallback)
spawn, login, game events, bot.game, time, weather, health, food, experienceyes, from the taptap
bot.entities, bot.players, blocks and world (blockAt, findBlocks, raytrace, waitForChunksToLoad)yes, from the taptap
chat / message / messagestr events, title, bossbar, scoreboard, tablist, sound, particle, explosionyes, from the taptap
bot.inventory, heldItem (reads)yestap
bot.entity position / velocity / yaw / pitch / onGroundyes, from the echoed player_auth_input (1-2 ticks behind)tap
setControlState, getControlState, clearControlStatesyesinput (virtual W/A/S/D/SPACE/SHIFT/CTRL keys)
mineflayer-pathfinder, collectblock, pvpyes, through controls, look and attackinput, look
look, lookAtyes; resolve when the echo converges (< 0.5°)look (closed-loop raw mouse)
setQuickBarSlot, equip of a hotbar itemyesslot (keys 1-9)
chat, whisper, commandsyes, uuid preservedsend (T/SLASH + text + ENTER, uuid lost)
dig, stopDiggingyes; crack animation on the client, completes on update_block → airlook, button (mouse)
placeBlock, activateBlock, activateItem, deactivateItem, consume, bowsyes (look, then use)look, button
attack, activateEntity, mount, dismountyeslook, button, input
toss of the held itemyesaction (drop / dropAll)
openContainer, openChest, openFurnace, closeWindowyes (close = ESC)look, button
clickWindow, moveSlotItem, transfer, deposit, withdraw, putAway, craft, equip from the main inventory / armor / off-hand, unequip, tossStack, creative.setInventorySlot, anvil / enchanting / villager / smithingyes, through the client's ItemStackNetManagerstack (none: ERR_BEDD_UNSUPPORTED)
writeBook, updateSign, respawn, bed UI sleep / wakeno (screen-level)—
bot.end, quitdetach only; the client stays in its world—
auth, username, host, port, viewDistance optionsignored with a warning (the client owns them)—

Notes: activateItem(true) (off-hand) uses the main hand, the client has no off-hand use button; activateItem of an item that is not used by holding (throwables, a fishing rod) is one click; bot.respawn() is a no-op with a warning (the user clicks Respawn, so respawn: true never throws from the health listener); bot.craft keeps upstream's new Error(err) wrapping, so check err.message there when the surface did not refuse it first.

Degraded: pathfinder sees 1-2 ticks of lag (prediction is overwritten by the echo); the client's own raycast decides what a click hits, so an action can land on an adjacent block or entity (bedboo sees it on update_block / entity events); flight via a double-tap jump; input pauses while a screen is open; a late attach depends on the journal (complete:false → chunks missing until re-entered); a world change on the client ends the bot (LINK_DOWN).

Singleplayer and LAN worlds

With tapLoopback (on by default once tap is), BeDD also taps a world the client hosts itself: singleplayer or a LAN world. Those packets never hit the network, so BeDD re-serializes them (Packet::write, frame flag reserialized) and filters them to the local player, so LAN guests' traffic is not mixed in. Everything in the coverage table works the same; the HELLO marker says link.type: 'local'.

Security

  • The tap exposes the session. A tap client reads everything the logged-in player sees and does: chat, positions, inventory, the world. The tap pipe accepts one local client of the current Windows user (PIPE_REJECT_REMOTE_CLIENTS, current-user DACL), and it only exists when tap is on.
  • send and stack act as the account. send posts chat and commands as the logged-in player, stack moves its items. Both are off by default (allowSend, allowStack); send is limited to the sendAllow ids ([9, 77]), and login, player_auth_input, item stack and similar ids (1, 30, 144, 147) are always refused.
  • Turn these on only in instances you use for automation, and never let a manager or remote tool toggle them.

Options and bot.bedrock.bedd

createBot({ transport: 'bedd', bedd: { pipe, tapPipe?, connect?, timeoutMs?, modules? } }):

option
bedd.pipethe control pipe, \\.\pipe\<pipeName> (default $BEDBOO_BEDD_PIPE, else \\.\pipe\BeDD)
bedd.tapPipethe tap pipe (default <pipe>-tap)
bedd.connect(path) => Duplex, to connect over something else (e.g. a unix socket)
bedd.timeoutMscontrol request timeout (default 5000 ms; an op's own timeoutMs gets 2 s more)
bedd.modulesthe lib/bedd modules to attach (default, in this order: authority, router, router_actions, follower, surface, inventory)
  • bot._bedrock.mode === 'bedd'.
  • bot.bedrock.bedd.caps: { [op]: 'present' | 'disabled' | 'absent' } for tap, world, look, input, slot, button, action, send, stack (and the protocol-1 ops key, text, mouse, raw, … once ping answers). Without a control pipe every op is absent and the bot only observes.
  • bot.bedrock.bedd.supports(name): whether a mineflayer API ('dig', 'craft', 'creative.setInventorySlot') or a BeDD op works with these caps.
  • bot.bedrock.bedd.observed: { yaw, pitch, tick } of the last echoed player_auth_input, null before the first.
  • Errors: BeddUnsupportedError (code: 'ERR_BEDD_UNSUPPORTED', bedd: true, feature, missing); an unsupported packet also emits 'bedd_unsupported_packet' { name, feature } on bot, once per name.
  • Client events besides the usual ones: 'serverbound' { name, params, reserialized } (what the client sent), 'bedd_marker' (tap markers) and 'warning' (a packet that did not decode; the stream goes on).

Roadmap

  • A drive / puppet mode (suppress, inject and pin the pose), once pinning is shown to survive client reconciliation.
  • Screen-level ops: the respawn button, signs and books.
  • A connect op so bedboo can make the client join a given server.
  • A second split-screen subclient.