This commit is contained in:
sebseb7
2026-05-21 09:50:06 +02:00
parent a02a1758b5
commit 373378de15
3 changed files with 240 additions and 39 deletions

View File

@@ -9,15 +9,19 @@ This document provides a comprehensive mapping of all the classes, functions, an
- [Architecture & Interaction Diagrams](#-architecture--interaction-diagrams) - [Architecture & Interaction Diagrams](#-architecture--interaction-diagrams)
- [High-Level Core System](#1-high-level-core-system-diagram) - [High-Level Core System](#1-high-level-core-system-diagram)
- [Client Handoff Flow](#2-client-handoff-flow-diagram) - [Client Handoff Flow](#2-client-handoff-flow-diagram)
- [MITM Sniffer Architecture](#3-mitm-sniffer-architecture-diagram) - [Spectator Watch Flow](#3-spectator-watch-flow-diagram)
- [MITM Sniffer Architecture](#4-mitm-sniffer-architecture-diagram)
- [Cross-Cutting Behavior](#-cross-cutting-behavior)
- [Detailed File Mapping](#-detailed-file-mapping) - [Detailed File Mapping](#-detailed-file-mapping)
- [Root Scripts & Configuration](#1-root-scripts--configuration) — `src/index.js`, `src/config.js` - [Root Scripts & Configuration](#1-root-scripts--configuration) — `src/index.js`, `src/config.js`
- [session — Session State Machine](#2-session--session-state-machine) — `SessionManager`, `ServerConnection`, `ChunkAckManager`, `MovementRelay`, `handoffFlow` - [session — Session State Machine](#2-session--session-state-machine) — `SessionManager`, `ServerConnection`, `BotIdleBehavior`, `ChunkAckManager`, `MovementRelay`, `handoffFlow`
- [proxy — Client Connection Proxy](#3-proxy--client-connection-proxy) — `ProxyServer`, `ClientBridge` - [proxy — Client Connection Proxy](#3-proxy--client-connection-proxy) — `ProxyServer`, `ClientBridge`
- [state — World State Caching](#4-state--world-state-caching) — `WorldStateCache`, `ChunkCache`, `EntityCache`, `PlayerStateCache`, `InventoryCache`, `MiscCache`, `JoinSyncCache`, `WorldBorderCache`, `ScoreboardCache` - [spectator — Watch-Only Multi-Client Proxy](#4-spectator--watch-only-multi-client-proxy) — `SpectatorProxyServer`, `SpectatorHub`, `spectatorPackets`
- [replay — Client Handoff Replay](#5-replay--client-handoff-replay) — `StateReplayer`, `replayChunks`, `replayHelpers` - [state — World State Caching](#5-state--world-state-caching) — `WorldStateCache`, `ChunkCache`, `chunkMerge`, entity/player/inventory/misc caches
- [utils — Helper Utilities](#6-utils--helper-utilities) — `angles`, `chatRelay`, `clientDisconnect`, `handoffSync`, `logger`, `positionSync` - [replay — Client Handoff Replay](#6-replay--client-handoff-replay) — `StateReplayer`, `replayChunks`, `replayHelpers`
- [sniffer — MITM Packet Sniffer](#7-sniffer--mitm-packet-sniffer) — `MitmProxy`, `TransparentProxy`, `StreamTap`, `PacketLog`, and relay modules - [utils — Helper Utilities](#7-utils--helper-utilities) — `angles`, `chatRelay`, `clientDisconnect`, `handoffSync`, `logger`, `positionSync`
- [constants — Shared Packet Sets](#8-constants--shared-packet-sets) — `rawPackets`, `spectatorPackets`
- [sniffer — MITM Packet Sniffer](#9-sniffer--mitm-packet-sniffer) — `MitmProxy`, `TransparentProxy`, `StreamTap`, `PacketLog`, and relay modules
--- ---
@@ -25,7 +29,7 @@ This document provides a comprehensive mapping of all the classes, functions, an
### 1. High-Level Core System Diagram ### 1. High-Level Core System Diagram
This diagram shows how the core components of FlayerProxy (`SessionManager`, `ProxyServer`, `ServerConnection`, `WorldStateCache`, `StateReplayer`, and `ClientBridge`) cooperate to manage the proxy's dual-mode lifecycle. This diagram shows how the core components cooperate across **play** (single client, port 25566), **spectator** (multi-client watch-only, port 25568), and **bot mode** (idle behaviour when no player is connected).
```mermaid ```mermaid
graph TD graph TD
@@ -33,31 +37,42 @@ graph TD
classDef helper fill:#f5f0d4,stroke:#7a6b1a,stroke-width:1px; classDef helper fill:#f5f0d4,stroke:#7a6b1a,stroke-width:1px;
classDef external fill:#e1d5e7,stroke:#9673a6,stroke-width:1px; classDef external fill:#e1d5e7,stroke:#9673a6,stroke-width:1px;
Client["Minecraft Client (Port 25566)"]:::external Client["Play Client (25566, max 1)"]:::external
Specs["Spectator Clients (25568, max N)"]:::external
Server["Target Minecraft Server"]:::external Server["Target Minecraft Server"]:::external
subgraph FlayerProxy ["FlayerProxy Core"] subgraph FlayerProxy ["FlayerProxy Core"]
SM["[SessionManager]"]:::main SM["[SessionManager]"]:::main
SC["[ServerConnection]"]:::main SC["[ServerConnection]"]:::main
PS["[ProxyServer]"]:::main PS["[ProxyServer]"]:::main
SPS["[SpectatorProxyServer]"]:::main
SH["[SpectatorHub]"]:::main
WSC["[WorldStateCache]"]:::main WSC["[WorldStateCache]"]:::main
SR["[StateReplayer]"]:::main SR["[StateReplayer]"]:::main
CB["[ClientBridge]"]:::main CB["[ClientBridge]"]:::main
IB["[BotIdleBehavior]"]:::helper
end end
SM -->|Orchestrates| SC SM -->|Orchestrates| SC
SM -->|Orchestrates| PS SM -->|Orchestrates| PS
SM -->|Orchestrates| SPS
SM -->|Orchestrates| WSC SM -->|Orchestrates| WSC
SM -->|Orchestrates| SR SM -->|Orchestrates| SR
SM -->|Coordinates Handoff| CB SM -->|Handoff| CB
SPS -->|on join| SH
SH -->|Uses| SR
SH -->|Fans out S2C from| SC
SC -->|Holds session with| Bot["Mineflayer Bot"]:::external SC -->|Holds session with| Bot["Mineflayer Bot"]:::external
SC -->|BOT_MODE| IB
IB -->|onSwing → botVisual| SH
Bot -->|Connects to| Server Bot -->|Connects to| Server
SC -->|Captures play packets to| WSC SC -->|Captures play packets to| WSC
PS -->|Listens for incoming| Client PS -->|Single slot login| Client
SR -->|Replays cache from WSC to| Client SR -->|replay / replaySpectator| Client
CB -->|Pipes C2S / S2C play packets| Client SR -->|replaySpectator| Specs
CB -->|Pipes C2S / S2C play packets| SC CB -->|Bidirectional play| Client
CB -->|Bidirectional play| SC
``` ```
--- ---
@@ -79,6 +94,7 @@ sequenceDiagram
Note over SM,SC: State: BOT_MODE (Bot AI holds session) Note over SM,SC: State: BOT_MODE (Bot AI holds session)
Player->>PS: Connect to Proxy (Port 25566) Player->>PS: Connect to Proxy (Port 25566)
Note over PS: login reserves single activeClient slot
PS->>SM: _onClientConnect(client) PS->>SM: _onClientConnect(client)
Note over SM: State -> HANDOFF Note over SM: State -> HANDOFF
SM->>SC: setBotControl(false) [Disable Bot physics/AI] SM->>SC: setBotControl(false) [Disable Bot physics/AI]
@@ -110,7 +126,40 @@ sequenceDiagram
--- ---
### 3. MITM Sniffer Architecture Diagram ### 3. Spectator Watch Flow Diagram
Spectators connect on a **separate port** (default 25568). They receive a spectator replay, then a read-only fan-out of upstream S2C packets. Movement is blocked; the camera is locked to the bot entity. Idle arm swings are synthesized locally (the server does not echo the bot's own swing).
```mermaid
sequenceDiagram
autonumber
actor Spec as Spectator Client
participant SPS as SpectatorProxyServer
participant SH as SpectatorHub
participant SR as StateReplayer
participant SC as ServerConnection
participant IB as BotIdleBehavior
Note over SC: BOT_MODE (bot control + idle)
Spec->>SPS: Connect (Port 25568)
SPS->>SH: addSpectator(client)
SH->>SR: replaySpectator(client)
SR->>Spec: login, terrain, entities (spectator gamemode + camera)
SH->>Spec: camera lock + position snap to bot
SH->>SC: listen serverPacket + botVisual
loop Live watch
SC->>SH: serverPacket (chunks, entities, position, …)
SH->>Spec: forward S2C (writeRaw where needed)
IB->>SC: swingArm → emit botVisual animation
SC->>SH: botVisual animation
SH->>Spec: entity swing animation
Spec--xSH: movement C2S dropped / camera re-lock
end
```
---
### 4. MITM Sniffer Architecture Diagram
The Packet Sniffer has two modes of operation: The Packet Sniffer has two modes of operation:
@@ -141,13 +190,51 @@ graph LR
--- ---
## 🔑 Cross-Cutting Behavior
Summary of policies that span multiple modules (not obvious from individual class tables).
### Ports and connection policy
| Port (default) | Listener | Max clients | When accepted |
| :--- | :--- | :--- | :--- |
| **25566** | [ProxyServer](file:///home/seb/flayerproxy/src/proxy/ProxyServer.js) | **1** | `BOT_MODE` only; slot reserved on `login`, released on reject/disconnect |
| **25568** | [SpectatorProxyServer](file:///home/seb/flayerproxy/src/proxy/SpectatorProxyServer.js) | **20** (configurable) | Upstream connected and not `INIT`/`HANDOFF`; allowed in `BOT_MODE` (bot control) or `CLIENT_MODE` |
Disable spectators with `spectator.enabled: false` in `config.json`.
### Chunk cache vs live forwarding
| Phase | `map_chunk` behavior |
| :--- | :--- |
| **Bot session (cache)** | [ChunkCache](file:///home/seb/flayerproxy/src/state/ChunkCache.js) stores only chunks within view distance of the bot view center (`update_view_position` or player position + `update_view_distance`). Out-of-view chunks are skipped on ingest and pruned via `forgetOutsideView`. |
| **Handoff replay** | [StateReplayer](file:///home/seb/flayerproxy/src/replay/StateReplayer.js) + `getChunksForReplay()` send **only** in-view cached chunks (nearest-first). |
| **CLIENT_MODE bridge** | [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js) forwards **all** upstream `map_chunk` packets. May send `update_view_position` so the client accepts chunks outside its local cache radius. |
Chunks are **server-pushed**; the bot/client mainly acks via `chunk_batch_received` and movement/view packets.
### Block/light merge and `structuredClone`
`map_chunk` binary fields (`chunkData`, heightmaps, etc.) must stay as Node `Buffer` instances for prismarine-chunk / smart-buffer. `structuredClone` in the cache path can turn them into `Uint8Array`, which breaks merges (`Invalid Buffer provided in SmartBufferOptions`). [chunkMerge.js](file:///home/seb/flayerproxy/src/state/chunkMerge.js) normalizes via `asBuffer()` / `normalizeMapChunkPacket()` before column load and merge.
### Spectator watch (movement and visuals)
- **Movement:** Vanilla spectator free-cam is largely client-side. The proxy cannot rely on dropping C2S alone; [SpectatorHub](file:///home/seb/flayerproxy/src/spectator/SpectatorHub.js) locks the camera to the bot entity (`camera` packet), snaps position on movement C2S, and runs a 1s correction loop.
- **Idle swing:** The server does not echo the bots own arm swing. [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js) → [ServerConnection._emitBotSwingAnimation](file:///home/seb/flayerproxy/src/session/ServerConnection.js) → `botVisual` → spectator `animation` fan-out.
### Bot idle (no play client)
While in `BOT_MODE` with bot control enabled, [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js) runs random look / sneak / swing on a timer (`config.bot.antiAfk*`). Started/stopped with [ServerConnection.setBotControl](file:///home/seb/flayerproxy/src/session/ServerConnection.js).
---
## 🗂️ Detailed File Mapping ## 🗂️ Detailed File Mapping
### 1. Root Scripts & Configuration ### 1. Root Scripts & Configuration
#### 📄 [src/index.js](file:///home/seb/flayerproxy/src/index.js) #### 📄 [src/index.js](file:///home/seb/flayerproxy/src/index.js)
The main entry point for the application. Loads system config and handles errors, starts the core [SessionManager](file:///home/seb/flayerproxy/src/session/SessionManager.js), hooks process event signals (`SIGINT`, `SIGTERM`) for a graceful shutdown, and captures `uncaughtException` / `unhandledRejection` warnings. The main entry point for the application. Loads system config, logs play and spectator proxy ports, starts [SessionManager](file:///home/seb/flayerproxy/src/session/SessionManager.js), hooks `SIGINT`/`SIGTERM` for graceful shutdown, and captures `uncaughtException` / `unhandledRejection`.
| Function | Description | | Function | Description |
|---|---| |---|---|
@@ -159,7 +246,9 @@ Handles configuration loading, validation, and parsing.
| Function | Description | | Function | Description |
|---|---| |---|---|
| `loadConfig()` | Synchronously reads `config.json`, validates host, port, version, and auth configuration, applies default options, and returns the configuration object. | | `loadConfig()` | Synchronously reads `config.json`, validates host, port, version, and auth configuration, applies defaults for `proxy`, `spectator`, `bot` (anti-Afk intervals), and `cache`, and returns the configuration object. |
**Default config sections:** `proxy` (port 25566, `maxClients: 1`), `spectator` (port 25568, `maxClients: 20`, `enabled: true`), `bot` (`antiAfk`, `antiAfkMinInterval`, `antiAfkMaxInterval`, `viewDistance`).
--- ---
@@ -171,14 +260,17 @@ Orchestrates the dual-mode proxy state machine: `INIT` ↔ `BOT_MODE` ↔ `HANDO
| Method | Description | | Method | Description |
|---|---| |---|---|
| `constructor(config)` | Initializes state machine with configuration. | | `constructor(config)` | Initializes state machine, play proxy, optional spectator proxy/hub, and replayer. |
| `start()` | Establishes connections to the upstream Minecraft server and starts the proxy server. | | `start()` | Connects upstream bot, starts play proxy (25566), and spectator proxy (25568) if enabled. |
| `_scheduleReconnect(delaySec)` | Schedules a timed reconnect sequence if the connection is lost. | | `_scheduleReconnect(delaySec)` | Schedules a timed reconnect sequence if the connection is lost. |
| `_setupServerEvents()` | Hooks upstream server events (`connected`, `disconnected`, `kicked`, `error`, `death`, `respawn`). | | `_setupServerEvents()` | Hooks upstream server events (`connected`, `disconnected`, `kicked`, `error`, `death`, `respawn`). |
| `_primeChunksNearBot()` | Triggers server movement packets to verify that the chunk cache is loaded near the bot before handing off. | | `_primeChunksNearBot()` | Triggers server movement packets to verify that the chunk cache is loaded near the bot before handing off. |
| `_refreshClientAfterBotRespawn()` | Re-aligns position and client view in case the bot respawns while a client is connected. | | `_refreshClientAfterBotRespawn()` | Re-aligns position and client view in case the bot respawns while a client is connected. |
| `_onClientConnect(client)` | Initiates the handoff sequence to transition from `BOT_MODE` to `CLIENT_MODE`. | | `_clientSlotStatus()` | Returns whether the play port may accept a new login (single client, `BOT_MODE` only). |
| `_cleanupClient()` | Stops the packet bridge and cleans up client-related event listeners. | | `_spectatorSlotStatus()` | Returns whether the spectator port may accept logins (connected, not `INIT`/`HANDOFF`; `BOT_MODE` with bot control or `CLIENT_MODE`). |
| `_rejectClient(client, reason)` | Kicks play client and releases `ProxyServer.activeClient` slot. |
| `_onClientConnect(client)` | Validates play slot, then handoff `BOT_MODE``CLIENT_MODE`. |
| `_cleanupClient()` | Stops bridge, releases play client slot, clears `currentClient`. |
| `_transitionTo(newState)` | Transitions the machine state and logs status summaries. | | `_transitionTo(newState)` | Transitions the machine state and logs status summaries. |
| `stop()` | Gracefully halts all services. | | `stop()` | Gracefully halts all services. |
@@ -192,8 +284,9 @@ Manages the persistent Mineflayer bot connection to the target server.
| `connect()` | Spawns the bot connection via Mineflayer. | | `connect()` | Spawns the bot connection via Mineflayer. |
| `_setupConfigCapture()` | Caches configuration-phase registry data and tags. | | `_setupConfigCapture()` | Caches configuration-phase registry data and tags. |
| `_setupPacketCapture()` | Hooks play packets and routes them directly to the state cache. | | `_setupPacketCapture()` | Hooks play packets and routes them directly to the state cache. |
| `_setupBotEvents()` | Listens for bot lifecycle events (`spawn`, `end`, `kicked`, `error`, `death`, and chat logging). | | `_setupBotEvents()` | Listens for bot lifecycle events; creates [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js) on spawn. |
| `setBotControl(enabled)` | Enables/disables physics and AI on the Mineflayer bot. | | `_emitBotSwingAnimation(hand)` | Emits `botVisual` (`animation` packet) so spectators see idle swings (server does not echo self-swing). |
| `setBotControl(enabled)` | Enables/disables physics; starts/stops [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js). |
| `setClientDrivesChunkBatchAck(clientDrives)` | Delegates chunk batch acknowledgement control between client and Mineflayer. | | `setClientDrivesChunkBatchAck(clientDrives)` | Delegates chunk batch acknowledgement control between client and Mineflayer. |
| `flushChunkBatchAck()` | Unblocks the server chunk sender. | | `flushChunkBatchAck()` | Unblocks the server chunk sender. |
| `refreshProxyClientPermissions(client)` | Sends player permissions status packets. | | `refreshProxyClientPermissions(client)` | Sends player permissions status packets. |
@@ -204,6 +297,8 @@ Manages the persistent Mineflayer bot connection to the target server.
| `writeToServer(name, data)` | Writes raw packets directly upstream. | | `writeToServer(name, data)` | Writes raw packets directly upstream. |
| `disconnect()` | Safely disconnects the bot. | | `disconnect()` | Safely disconnects the bot. |
**Events:** `connected`, `disconnected`, `kicked`, `error`, `death`, `respawn`, `serverPacket` `(name, data, buffer)`, `botVisual` `(name, data)` (synthetic S2C for spectators).
#### 🧩 [ChunkAckManager](file:///home/seb/flayerproxy/src/session/ChunkAckManager.js) `class` #### 🧩 [ChunkAckManager](file:///home/seb/flayerproxy/src/session/ChunkAckManager.js) `class`
Intercepts Mineflayer's chunk batch acknowledgement listeners to prevent double-acknowledging packets during the client session. Intercepts Mineflayer's chunk batch acknowledgement listeners to prevent double-acknowledging packets during the client session.
@@ -229,6 +324,20 @@ Intercepts Mineflayer's chunk batch acknowledgement listeners to prevent double-
|---|---| |---|---|
| `performHandoff({...})` | Coordinates the sequential handoff sequence: installs temporary upstream forwarding rules, primes nearby chunks, triggers the [StateReplayer](file:///home/seb/flayerproxy/src/replay/StateReplayer.js), aligns player coordinates/permissions, and spawns the [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js). | | `performHandoff({...})` | Coordinates the sequential handoff sequence: installs temporary upstream forwarding rules, primes nearby chunks, triggers the [StateReplayer](file:///home/seb/flayerproxy/src/replay/StateReplayer.js), aligns player coordinates/permissions, and spawns the [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js). |
#### 🧩 [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js) `class`
Random look / sneak / swing while the bot holds the session (`BOT_MODE`, no play client). Started from [ServerConnection.setBotControl(true)](file:///home/seb/flayerproxy/src/session/ServerConnection.js).
| Method | Description |
|---|---|
| `constructor(bot, botConfig, hooks)` | Optional `onSwing(hand)` hook for spectator animation relay. |
| `start()` / `stop()` | Enables/disables timed idle actions (`config.bot.antiAfk`). |
| `_scheduleNext()` | Random delay between `antiAfkMinInterval` and `antiAfkMaxInterval`. |
| `_tick()` | Picks random action: look, sneak, or swing. |
| `_randomLook()` | `bot.look()` with small yaw/pitch delta. |
| `_randomSneak()` | Toggles sneak via `setControlState` for a random duration. |
| `_randomSwing()` | `bot.swingArm()` then `onSwing` callback. |
--- ---
### 3. proxy — Client Connection Proxy ### 3. proxy — Client Connection Proxy
@@ -239,10 +348,11 @@ Listens for connection attempts from standard Minecraft Java clients.
| Method | Description | | Method | Description |
|---|---| |---|---|
| `constructor(config, onClientConnect, worldState)` | Initializes proxy server with config, client callback, and world state. | | `constructor(config, onClientConnect, worldState, canAcceptClient)` | Play proxy; optional `canAcceptClient()` gate from [SessionManager](file:///home/seb/flayerproxy/src/session/SessionManager.js). |
| `start()` | Initializes the local minecraft-protocol server (`mc.createServer`), configures pre-login listeners to replay raw config packets, and registers joining players. | | `releaseClient(client)` | Clears `activeClient` if it matches (used on reject/disconnect). |
| `start()` | `mc.createServer` on `config.proxy.port`; **reserves single slot on `login`**; replays raw config on `login_acknowledged`; `playerJoin` only for reserved client. |
| `updateRegistryCodec(codec)` | Replaces the registry codec object in the protocol handler options. | | `updateRegistryCodec(codec)` | Replaces the registry codec object in the protocol handler options. |
| `stop()` | Halts client listening sockets. | | `stop()` | Disconnects active client and closes listener. |
#### 🧩 [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js) `class` #### 🧩 [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js) `class`
@@ -261,9 +371,59 @@ Manages bidirectional packet pipelines when in `CLIENT_MODE`.
| `_shouldForwardPlayerInfo(data)` | Filters latency updates for unknown players. | | `_shouldForwardPlayerInfo(data)` | Filters latency updates for unknown players. |
| `stop()` | Tears down the forwarding pipe. | | `stop()` | Tears down the forwarding pipe. |
**Play client forwarding:** All upstream `map_chunk` packets are forwarded (no radius filter). `update_view_position` may be sent before chunks outside the client cache radius.
--- ---
### 4. state — World State Caching ### 4. spectator — Watch-Only Multi-Client Proxy
#### 🧩 [SpectatorProxyServer](file:///home/seb/flayerproxy/src/proxy/SpectatorProxyServer.js) `class`
Separate `mc.createServer` on `config.spectator.port` (default **25568**). Allows **multiple** simultaneous spectators; no upstream connection per spectator.
| Method | Description |
|---|---|
| `constructor(config, hub, worldState, canAcceptClient)` | Spectator listener with slot/status callback from [SessionManager._spectatorSlotStatus](file:///home/seb/flayerproxy/src/session/SessionManager.js). |
| `start()` | Binds port, replays raw config packets, calls `hub.addSpectator` on `playerJoin`. |
| `updateRegistryCodec(codec)` | Same registry injection as play proxy. |
| `stop()` | Closes spectator listener. |
#### 🧩 [SpectatorHub](file:///home/seb/flayerproxy/src/spectator/SpectatorHub.js) `class`
Manages spectator clients: replay, S2C fan-out, movement block, camera lock.
| Method | Description |
|---|---|
| `constructor(serverConn, worldState, replayer, config)` | Subscribes to `serverConn` `serverPacket` and `botVisual` events. |
| `addSpectator(client)` | `replaySpectator`, camera lock, position snap, join fan-out set. |
| `removeSpectator(client)` | Removes client; detaches fan-out when last spectator leaves. |
| `kickAll(reason)` / `stop()` | Disconnects all spectators; removes listeners. |
| `_installClientGuard(client, state)` | Whitelist C2S only; on movement packets, re-lock camera + snap position. |
| `_lockCamera(client)` | Sends `camera` with bot `entityId`. |
| `_snapPosition(client, state)` | Sends clientbound `position` from bot entity. |
| `_startSnapLoop()` / `_stopSnapLoop()` | Periodic camera/position correction (1s). |
| `_forwardToSpectators(name, data, buffer)` | Fans upstream S2C to all spectators (`writeRaw` for chunk packets). |
#### 📄 [spectatorPackets.js](file:///home/seb/flayerproxy/src/constants/spectatorPackets.js) `constants`
| Export | Description |
|---|---|
| `SPECTATOR_GAMEMODE` | Gamemode id `3` for replay. |
| `SPECTATOR_ALLOWED_C2S` | Whitelist: `chunk_batch_received`, `teleport_confirm`, `keep_alive`, etc. |
| `SPECTATOR_BLOCKED_S2C` | Dropped S2C fan-out (e.g. `tracked_waypoint` — mid-join UPDATE without prior TRACK crashes clients). |
| `SPECTATOR_MOVEMENT_C2S` | Movement packets that trigger camera re-lock. |
| `ANIMATION_SWING_MAIN_HAND` / `ANIMATION_SWING_OFF_HAND` | Clientbound `animation` ids for idle swing relay. |
**ServerConnection events used by spectators:**
| Event | Payload | Description |
|---|---|---|
| `serverPacket` | `(name, data, buffer)` | Same stream as play cache (from upstream bot). |
| `botVisual` | `(name, data)` | Synthetic S2C (e.g. idle `animation`) not echoed by server. |
---
### 5. state — World State Caching
#### 🧩 [WorldStateCache](file:///home/seb/flayerproxy/src/state/WorldStateCache.js) `class` #### 🧩 [WorldStateCache](file:///home/seb/flayerproxy/src/state/WorldStateCache.js) `class`
@@ -278,27 +438,41 @@ Master coordinator for world cache segments. Integrates and clears sub-caches on
| `getRawConfigPacketsForReplay()` | Filters and returns config-phase buffers for client replay. | | `getRawConfigPacketsForReplay()` | Filters and returns config-phase buffers for client replay. |
| `hasRawConfigPackets()` | Returns `true` if raw configuration buffers are cached. | | `hasRawConfigPackets()` | Returns `true` if raw configuration buffers are cached. |
| `handleServerPacket(name, data, buffer)` | Evaluates incoming play packets and routes them to sub-caches. | | `handleServerPacket(name, data, buffer)` | Evaluates incoming play packets and routes them to sub-caches. |
| `_getChunkViewContext()` | Resolves view center (`update_view_position` or player position) + view distance for cache retention. |
| `_forgetChunksOutsideView()` | Drops cached chunks outside [isChunkWithinViewDistance](file:///home/seb/flayerproxy/src/utils/positionSync.js). |
| `getSummary()` | Returns cache sizing info for log monitoring. | | `getSummary()` | Returns cache sizing info for log monitoring. |
| `clear()` | Wipes all cached structures. | | `clear()` | Wipes all cached structures. |
#### 🧩 [ChunkCache](file:///home/seb/flayerproxy/src/state/ChunkCache.js) `class` #### 🧩 [ChunkCache](file:///home/seb/flayerproxy/src/state/ChunkCache.js) `class`
Manages loaded map chunks, light maps, and block overlays using an LRU cache. Manages loaded map chunks, light maps, and block overlays using an LRU cache. Merges block/light updates via [chunkMerge.js](file:///home/seb/flayerproxy/src/state/chunkMerge.js) (prismarine-chunk columns).
| Method | Description | | Method | Description |
|---|---| |---|---|
| `constructor(maxChunks)` | Initializes chunk storage with LRU capacity limit. | | `constructor(maxChunks, options)` | LRU storage; `version` + `getWorldBounds()` for column decode. |
| `_key(x, z)` | Computes string key for map lookups. | | `_key(x, z)` | Computes string key for map lookups. |
| `handleMapChunk(data, rawBuffer)` | Caches chunk data and marks it as active in the LRU tracking queue. | | `forgetOutsideView(centerChunkX, centerChunkZ, viewDistance)` | Removes chunks outside server `ChunkTrackingView` distance. |
| `handleUpdateLight(data, rawBuffer)` | Caches light updates. | | `handleMapChunk(data, rawBuffer, view?)` | Skips store if outside view; prunes cache; normalizes Buffers after `structuredClone`. |
| `handleUpdateLight(data)` | Merges light into cached column via `applyUpdateLight`. |
| `handleUnloadChunk(data)` | Evicts chunk records from cache. | | `handleUnloadChunk(data)` | Evicts chunk records from cache. |
| `handleBlockChange(data)` | Appends single block changes as an overlay. | | `handleBlockChange(data)` | Merges single block into column; logs merge failures. |
| `handleMultiBlockChange(data)` | Appends multiblock edits as an overlay. | | `handleMultiBlockChange(data)` | Merges section block records into column. |
| `_buildChunkEntry(chunkData)` | Assembles raw data, block edits, and light arrays. | | `_ensureColumn(stored)` | Lazy-loads prismarine column from `map_chunk` packet data. |
| `getChunksForReplay(centerChunkX, centerChunkZ, viewDistance)` | Returns cached chunks sorting closest-first. | | `_syncPacketFromColumn(stored)` | Re-exports merged column to `packetData`. |
| `getChunksForReplay(centerChunkX, centerChunkZ, viewDistance)` | Returns in-view chunks only (same distance test as forget), nearest-first. |
| `hasChunkAtBlock(x, z)` | Verifies if a chunk is loaded. | | `hasChunkAtBlock(x, z)` | Verifies if a chunk is loaded. |
| `clear()` | Wipes chunk maps. | | `clear()` | Wipes chunk maps. |
#### 📄 [chunkMerge.js](file:///home/seb/flayerproxy/src/state/chunkMerge.js) `functions`
| Function | Description |
|---|---|
| `asBuffer(value)` | Coerces `Buffer` / `Uint8Array` for prismarine-chunk / smart-buffer. |
| `normalizeMapChunkPacket(packet)` | Fixes binary fields after `structuredClone`. |
| `loadColumnFromMapChunk(packet, version, worldBounds)` | Builds prismarine column from `map_chunk`. |
| `exportMapChunkPacket(column, packet)` | Dumps column back to `map_chunk` shape. |
| `applyBlockChange` / `applyUpdateLight` / `applyMultiBlockChange` | Merge incremental updates into column. |
#### 🧩 [EntityCache](file:///home/seb/flayerproxy/src/state/EntityCache.js) `class` #### 🧩 [EntityCache](file:///home/seb/flayerproxy/src/state/EntityCache.js) `class`
Tracks entities, positions, gear, and status effects. Tracks entities, positions, gear, and status effects.
@@ -427,16 +601,17 @@ Caches teams, scores, objectives, and scoreboard layouts.
--- ---
### 5. replay — Client Handoff Replay ### 6. replay — Client Handoff Replay
#### 🧩 [StateReplayer](file:///home/seb/flayerproxy/src/replay/StateReplayer.js) `class` #### 🧩 [StateReplayer](file:///home/seb/flayerproxy/src/replay/StateReplayer.js) `class`
Replays the cached world state to a connecting client. Replays the cached world state to a connecting play or spectator client.
| Method | Description | | Method | Description |
|---|---| |---|---|
| `constructor(worldState, serverConn)` | Initializes replayer with world state and server connection references. | | `constructor(worldState, serverConn)` | Initializes replayer with world state and server connection references. |
| `replay(client)` | Coordinates sequential packet delivery (see replay sequence below). | | `replaySpectator(client)` | Calls `replay(client, { spectator: true })` — gamemode 3, `camera` lock, no inventory. |
| `replay(client, options)` | Sequential packet delivery; `options.spectator` adjusts abilities/gamemode/camera. |
**`replay()` sequence:** **`replay()` sequence:**
@@ -471,9 +646,11 @@ Replays the cached world state to a connecting client.
| `splitMiscReplayPackets(packets)` | Splits misc packets into early configurations, level coordinates, and weather variables. | | `splitMiscReplayPackets(packets)` | Splits misc packets into early configurations, level coordinates, and weather variables. |
| `waitForClientTeleportConfirm(client)` | Awaits the client's `teleport_confirm` packet. | | `waitForClientTeleportConfirm(client)` | Awaits the client's `teleport_confirm` packet. |
**Spectator replay differences:** Sends `game_state_change` (gamemode 3), `camera` (bot entity id), flying ability flags; skips full inventory and permission `entity_status`.
--- ---
### 6. utils — Helper Utilities ### 7. utils — Helper Utilities
#### 📄 [angles.js](file:///home/seb/flayerproxy/src/utils/angles.js) `functions` #### 📄 [angles.js](file:///home/seb/flayerproxy/src/utils/angles.js) `functions`
@@ -528,7 +705,21 @@ Replays the cached world state to a connecting client.
--- ---
### 7. sniffer — MITM Packet Sniffer ### 8. constants — Shared Packet Sets
#### 📄 [rawPackets.js](file:///home/seb/flayerproxy/src/constants/rawPackets.js)
| Export | Description |
|---|---|
| `RAW_FORWARD_PACKETS` | Play packets forwarded with `writeRaw` (chunks, lights, view position, batch markers). Used by [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js) and [SpectatorHub](file:///home/seb/flayerproxy/src/spectator/SpectatorHub.js). |
#### 📄 [spectatorPackets.js](file:///home/seb/flayerproxy/src/constants/spectatorPackets.js)
See [spectator — Watch-Only Multi-Client Proxy](#4-spectator--watch-only-multi-client-proxy).
---
### 9. sniffer — MITM Packet Sniffer
#### 📄 [src/sniffer/index.js](file:///home/seb/flayerproxy/src/sniffer/index.js) #### 📄 [src/sniffer/index.js](file:///home/seb/flayerproxy/src/sniffer/index.js)

View File

@@ -14,6 +14,13 @@ const SPECTATOR_ALLOWED_C2S = new Set([
'ping_request', 'ping_request',
]); ]);
/**
* S2C packets not forwarded to spectators.
* tracked_waypoint (journeys/locator bar) is session-ordered (track → update);
* mid-join spectators only see updates and disconnect.
*/
const SPECTATOR_BLOCKED_S2C = new Set(['tracked_waypoint']);
/** Movement-ish C2S — trigger camera lock + position snap when received. */ /** Movement-ish C2S — trigger camera lock + position snap when received. */
const SPECTATOR_MOVEMENT_C2S = new Set([ const SPECTATOR_MOVEMENT_C2S = new Set([
'position', 'position',
@@ -33,6 +40,7 @@ const SPECTATOR_MOVEMENT_C2S = new Set([
module.exports = { module.exports = {
SPECTATOR_GAMEMODE, SPECTATOR_GAMEMODE,
SPECTATOR_ALLOWED_C2S, SPECTATOR_ALLOWED_C2S,
SPECTATOR_BLOCKED_S2C,
SPECTATOR_MOVEMENT_C2S, SPECTATOR_MOVEMENT_C2S,
ANIMATION_SWING_MAIN_HAND, ANIMATION_SWING_MAIN_HAND,
ANIMATION_SWING_OFF_HAND, ANIMATION_SWING_OFF_HAND,

View File

@@ -2,6 +2,7 @@ const { createLogger } = require('../utils/logger');
const { RAW_FORWARD_PACKETS } = require('../constants/rawPackets'); const { RAW_FORWARD_PACKETS } = require('../constants/rawPackets');
const { const {
SPECTATOR_ALLOWED_C2S, SPECTATOR_ALLOWED_C2S,
SPECTATOR_BLOCKED_S2C,
SPECTATOR_MOVEMENT_C2S, SPECTATOR_MOVEMENT_C2S,
} = require('../constants/spectatorPackets'); } = require('../constants/spectatorPackets');
const { const {
@@ -191,6 +192,7 @@ class SpectatorHub {
_forwardToSpectators(name, data, buffer) { _forwardToSpectators(name, data, buffer) {
if (this._spectators.size === 0) return; if (this._spectators.size === 0) return;
if (SPECTATOR_BLOCKED_S2C.has(name)) return;
const block = this._botBlockCoords(); const block = this._botBlockCoords();
const viewDistance = this._getViewDistance(); const viewDistance = this._getViewDistance();