A bot framework for
Pixel Worlds
Handles the full lifecycle — authentication via Steam, Android, or email, TCP socket communication with game servers, world parsing, A* pathfinding, and in-world actions.
Project Structure
All source code lives under src/, split into core logic, API clients, and utility helpers.
bot.ts — TCP client + actions
world.ts — tile grid parser
socialfirst_client.ts — token exchange
pixel-worlds/ ├── src/ │ ├── index.ts # Entry point │ ├── config.ts # App configuration │ ├── types/index.ts # Shared types & enums │ ├── core/ │ │ ├── session.ts # Auth + session bootstrap │ │ ├── bot.ts # TCP client, packet I/O, actions │ │ └── world.ts # World deserialization + tile queries │ ├── clients/ │ │ ├── playfab_client.ts # PlayFab REST (Steam/Android/Email) │ │ └── socialfirst_client.ts # SocialFirst token exchange │ └── utils/ │ ├── packets.ts # BSON packet builders │ ├── pathfinder.ts # A* pathfinding │ ├── logger.ts # Namespaced terminal logger │ ├── fetch_with_retry.ts # HTTP + SOCKS5 + retry │ ├── conqurent_queue.ts # Concurrency-limited task queue │ ├── receive_buffer.ts # TCP stream reassembly │ └── zstd_service.ts # Zstandard decompression ├── tsconfig.json └── package.json
Architecture
Three layered stages: authenticate credentials, establish a TCP connection, then act inside a world.
threads in config to control concurrency. Each session spawns its own Steam client, API clients, and Bot instance — they share no state.
Installation
Requires Node.js 18+ and TypeScript 5+. Install dependencies, configure your environment, then run.
# Concurrency THREADS=5 # Steam login STEAM_ACCOUNT=your_username STEAM_PASSWORD=your_password # Android login ANDROID_DEVICE_ID= # Email login PW_EMAIL=you@example.com PW_EMAIL_PASSWORD=your_password # API SF_API_KEY=your_key # Optional SOCKS5 proxy PROXY_URL=socks5://127.0.0.1:1080
Configuration
All config is typed via the Config interface. Use environment variables — never hardcode secrets.
| Field | Type | Description |
|---|---|---|
| threads | number | Max concurrent sessions in ConcurrentQueue |
| steam_account | string | Steam username |
| steam_password | string | Steam password |
| android_device_id | string? | Android device UUID for Android login |
| string? | Account email for email login | |
| email_password | string? | Account email password |
| user_agent | string | Unity HTTP user-agent string |
| unity_version | string | Unity version header sent with all requests |
| sf_api_key | string | SocialFirst client API key |
| proxy_url | string? | SOCKS5 proxy URL e.g. socks5://host:port |
| urls | object | PlayFab & SocialFirst endpoint URLs |
import { Config } from './types'; export const CONFIG: Config = { threads: Number(process.env.THREADS ?? 1), steam_account: process.env.STEAM_ACCOUNT ?? '', steam_password: process.env.STEAM_PASSWORD ?? '', android_device_id: process.env.ANDROID_DEVICE_ID ?? '', email: process.env.PW_EMAIL ?? '', email_password: process.env.PW_EMAIL_PASSWORD ?? '', user_agent: 'UnityPlayer/6000.3.11f1 ...', unity_version: '6000.3.11f1', sf_api_key: process.env.SF_API_KEY ?? '', proxy_url: process.env.PROXY_URL, urls: { /* PlayFab + SocialFirst endpoints */ }, };
Authentication Flow
Three login methods are supported. All ultimately exchange for a SocialFirst JWT used to authenticate the TCP connection.
Android: uses a normalized device UUID directly.
Email: uses PlayFab email/password credentials.
SessionTicket and EntityToken.init_account() initializes the account on SocialFirst's auth service, then exchange_token() swaps the PlayFab token for a socialFirstToken (JWT).Bot.set_credentials(jwt), then Bot.connect(host). On connection, the bot immediately sends a make_login(jwt) BSON packet to the game server.PixelWorldsSession
Manages the complete authentication lifecycle from credentials to a connected Bot.
import { PixelWorldsSession } from './core/session'; import { CONFIG } from './config'; const session = new PixelWorldsSession(CONFIG); // Returns { device_id, sf_token } const result = await session.start('android'); // or 'steam' | 'email'
{ device_id, sf_token } or connects the bot and resolves when in-world.Bot
The TCP client and action layer. Extends TypedEmitter<BotEvents> for type-safe event handling.
Status Lifecycle
Connection
Movement
Block Interaction
Chat
stop_spam() to halt.Events
State Properties
{x, y}DIR_LEFT (7) or DIR_RIGHT (3)World
Parses and holds the world's tile grid, collectables, and interactive items. Data is Zstd-decompressed and BSON-deserialized from the server packet.
Tile Queries
[x, y] tuples.PortalData.Properties
START, ABCPIKE)interface Tile { fore_id: number; // foreground block ID (0 = air) back_id: number; // background block ID pos: { x: number; y: number }; extra?: WorldItem; // interactive item on this tile (if any) }
Utilities
Shared helpers used across the codebase.
encode_packet, decode_packet, coordinate converters, and every packet constructor (make_login, make_movement_packet, make_hit_block, etc.).Uint16Array tile grids. Uses tile-cost weighting — solid blocks are impassable, scaffolding-type tiles have high cost. Returns Position[] | null.socks-proxy-agent.concurrency limit. add(task) queues work; onIdle() resolves when all tasks finish.debug | info | warn | error | silent. Methods: debug, info, success, warn, error, step, divider.zstd-codec for decompressing Zstandard-compressed world data received from the game server.Packet Constants
| Constant | Value | Description |
|---|---|---|
| ANIM_IDLE | 1 | Standing still animation |
| ANIM_WALK | 2 | Walking animation |
| ANIM_JUMP | 3 | Jumping animation |
| ANIM_START_FALL | 4 | Beginning to fall |
| ANIM_FALL | 5 | Falling animation |
| ANIM_PUNCH | 6 | Punch / hit animation |
| DIR_RIGHT | 3 | Facing right direction |
| DIR_LEFT | 7 | Facing left direction |
| TILE_WIDTH | 0.32 | World-unit width of one tile |
| TILE_HEIGHT | 0.32 | World-unit height of one tile |
Types & Enums
All shared types are defined in src/types/index.ts.
enum BotStatus { AUTHENTICATING = "authenticating", AUTH_FAILED = "auth_failed", OFFLINE = "offline", ONLINE = "online", MENU_READY = "menu_ready", AWAITING_READY = "awaiting_ready", IN_WORLD = "in_world", REDIRECTING = "redirecting", } enum NetStatus { NO_SOCKET, IDLE, CONNECTING, CONNECTED, DISCONNECTED } type Login_Type = 'steam' | 'android' | 'email'; type Log_Level_Key = 'debug' | 'info' | 'warn' | 'error' | 'silent';
interface Position { x: number; y: number } interface Tile { fore_id: number; back_id: number; pos: Position; extra?: WorldItem; } interface WorldItem { class: string; item_id: number; block_type: number; pos: Position; data: Record<string, any>; } interface Collectable { collectable_id: number; block_type: number; amount: number; inventory_type: number; pos_x: number; pos_y: number; is_gem: boolean; gem_type: number; }
Usage Example
A complete example: authenticate, connect, walk to a position, punch a block, and read the world.
import { CONFIG } from './config'; import { PixelWorldsSession } from './core/session'; import { Bot } from './core/bot'; const session = new PixelWorldsSession(CONFIG); const { sf_token } = await session.start('android'); const bot = new Bot(); bot.set_credentials(sf_token); // Optional: route through SOCKS5 proxy // bot.set_proxy('127.0.0.1', 1080); bot.on('status:in_world', async (b) => { console.log(`Entered world: ${b.world.name}`); // Walk to tile (10, 30) via A* pathfinding await b.walk_to_map(10, 30); // Punch tile to the right await b.manual_punch(1, 0); // Place a dirt block above await b.manual_place(0, -1, 2); // Chat await b.send_world_chat('Hello!'); // Read collectables const gems = b.world.gem_collectables; console.log(`Gems: ${gems.length}`); // Find all tiles with block ID 8 (e.g. lava) const positions = b.world.find_by_fore(8); b.stop(); }); await bot.connect('63.176.210.142');
Security Notes
Important practices to follow before deploying or committing code.
steam_password, email_password, sf_api_key — to a .env file and add .env to your .gitignore immediately. Exposed credentials in a zip or repository should be rotated right away.
src/utils/world_raw.json and src/utils/world.json are overwritten every time a world is loaded at runtime. These may contain positional and item data. Add them to .gitignore as well.
bot.set_proxy() routes the raw game server TCP socket through the SOCKS5 proxy, in addition to the HTTP requests handled by fetch_with_retry via socks-proxy-agent.
| File / Pattern | Action |
|---|---|
| .env | Add to .gitignore, never commit |
| src/utils/world*.json | Add to .gitignore (runtime output) |
| Hardcoded passwords | Replace with process.env.VAR |
| Exposed credentials | Rotate immediately on all affected services |
| account.txt | Treat as sensitive — restrict file permissions |