npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

beryl-sdk

v1.1.2

Published

TypeScript SDK for building Dark Ages tools

Readme

beryl-sdk

TypeScript SDK for Dark Ages tools that run in the browser. Connects to a local Beryl relay over WebSocket, discovers running game clients, and exposes an API for reading/writing packets, walking the character, reading files, and reading/writing process memory.

Install

npm install beryl-sdk

Beryl requires the Beryl relay to be running locally. The relay attaches to Dark Ages clients and exposes them over WebSocket; this SDK is the interface you use to interact with them.

Quick start

import { Beryl, Packet } from 'beryl-sdk';

const beryl = new Beryl();

beryl.on('ready', () => {
  console.log('Connected to relay');
});

beryl.on('clientConnected', ({ client, name }) => {
  console.log(`Client attached: ${name}`);

  client.on('sent', (packet) => {
    console.log('client -> server', packet.toString());
  });

  client.on('received', (packet) => {
    console.log('server -> client', packet.toString());
  });
});

beryl.on('clientDisconnected', ({ name }) => {
  console.log(`Client detached: ${name}`);
});

beryl.connect();

API

class Beryl

Extends an event emitter with the events listed below.

Methods

| Method | Description | | --- | --- | | connect(): void | Open the connection to the relay. If the connection drops, Beryl automatically reconnects every 2 seconds until close() is called. | | close(): void | Close the connection to the relay and cancel any pending reconnect. | | readFile(path: string): Promise<ArrayBuffer> | Read a file from the Dark Ages install directory. Paths are resolved relative to that directory and cannot escape it. Rejects with File not found, File read error, File request timed out, or WebSocket disconnected. | | getAllowedOrigins(): Promise<string[]> | Fetch the relay's allowed-origins list. Rejects with Settings request timed out or WebSocket disconnected. | | setAllowedOrigins(origins: string[]): void | Replace the relay's allowed-origins list. Throws if the relay is not connected. |

All request/response methods time out after 10 seconds.

Properties

| Property | Description | | --- | --- | | ready: boolean | true while the relay connection is open. |

Events (BerylEvents)

| Event | Payload | | --- | --- | | ready | () — relay connection opened. | | error | Error — relay connection error. | | clientConnected | { client: Client, name: string } | | clientDisconnected | { name: string } | | version | { relayVersion: string \| null, minVersion: string, updateRequired: boolean } — emitted on the first relay connect and again whenever the relay reports a different version. updateRequired is true if the relay is below the SDK's minimum (or doesn't report a version). |

class Client

Represents one attached game client. Obtained via the clientConnected event.

Methods

| Method | Description | | --- | --- | | send(packet: Packet): void | Inject a client→server packet. Throws if the client is not connected. | | receive(packet: Packet): void | Inject a server→client packet. Throws if the client is not connected. | | walk(direction: number): void | Move the character one tile (0=N, 1=E, 2=S, 3=W). Throws if the client is not connected. | | readMemory(offsets: number[], size: number): Promise<Uint8Array> | Follow a pointer chain and read size bytes. Rejects with Memory read timed out or WebSocket disconnected. | | writeMemory(offsets: number[], data: Uint8Array): Promise<boolean> | Follow a pointer chain and write data. Resolves true on success. Rejects with Memory write timed out or WebSocket disconnected. | | readFile(path: string): Promise<ArrayBuffer> | Read a file from the Dark Ages install directory. See Beryl.readFile for details. | | close(): void | Disconnect from this client. |

Memory operations time out after 10 seconds.

Walking has a dedicated method because the game maintains an internal walk sequence counter. Walking via packet injection would require manually updating that counter in game memory after every step to keep it in sync. walk() avoids that by invoking the game's own walk function directly, so the counter stays correct.

Properties

| Property | Description | | --- | --- | | name: string | Client identifier. | | port: number | WebSocket port the client listens on. |

Events (ClientEvents)

| Event | Payload | | --- | --- | | sent | Packet observed going client→server. | | received | Packet observed going server→client. | | focus | booleantrue when the game window gains OS focus, false when it loses focus. Emitted on connect with the current state, then on each change. | | close | () — connection closed. |

The focus signal comes from the Win32 foreground window and is reliable on Windows. Under Wine (e.g. running the game on macOS) the game never receives the Win32 focus change, so focus reports true on connect and never fires again.

class Packet

Cursor-based reader/writer for Dark Ages packets.

const p = new Packet(0x0e); // new packet with opcode 0x0e
p.writeByte(0);
p.writeString8('hello');
client.send(p);

const incoming = new Packet(bytes); // parse from Uint8Array
const opcode = incoming.opcode;
const type = packet.readByte();
const id = packet.readUint32();
const message = packet.readString8();

Constructors

| Signature | Description | | --- | --- | | new Packet(opcode: number) | Build an empty packet with the given opcode. | | new Packet(data: Uint8Array) | Parse an existing packet. opcode is data[0] and the cursor starts at the first body byte. |

Properties

| Property | Description | | --- | --- | | opcode: number | Packet opcode. | | position: number | Current cursor position within the body. |

Methods

| Method | Description | | --- | --- | | read(length: number): number[] | Read length bytes. | | readByte(): number | Read an unsigned byte. | | readSbyte(): number | Read a signed byte. | | readBoolean(): boolean | Read a byte as a boolean. | | readInt16(): number | Read a signed 16-bit integer. | | readUint16(): number | Read an unsigned 16-bit integer. | | readInt32(): number | Read a signed 32-bit integer. | | readUint32(): number | Read an unsigned 32-bit integer. | | readString8(): string | Read a string with a 1-byte length prefix. | | readString16(): string | Read a string with a 2-byte length prefix. | | write(bytes: number[]): void | Append raw bytes. | | writeByte(value: number): void | Write an unsigned byte. | | writeSbyte(value: number): void | Write a signed byte. | | writeBoolean(value: boolean): void | Write a byte as a boolean. | | writeInt16(value: number): void | Write a signed 16-bit integer. | | writeUint16(value: number): void | Write an unsigned 16-bit integer. | | writeInt32(value: number): void | Write a signed 32-bit integer. | | writeUint32(value: number): void | Write an unsigned 32-bit integer. | | writeString(value: string): void | Write a string with no length prefix. | | writeString8(value: string): void | Write a string with a 1-byte length prefix. | | writeString16(value: string): void | Write a string with a 2-byte length prefix. | | buffer(): Uint8Array | The packet's bytes. | | toString(): string | Space-separated uppercase hex. |

Read methods advance position. Reads past the end return a zero value of the matching type: 0 for numbers, false for booleans, '' for strings, [] for read(length).

License

MIT