@obsidian-bridge/core
v0.1.1
Published
Core utilities and types for Obsidian Bridge - Minecraft Java ↔ Bedrock Edition protocol bridge
Maintainers
Readme
@obsidian-bridge/core
⚠️ WARNING: BETA VERSION
This package is currently in beta and under active development.
Known limitations:
- API is unstable and may change without notice
- Test coverage is incomplete - some tests may fail
- Features are experimental and not fully tested
- Breaking changes may occur between beta versions
Not recommended for production use. Use at your own risk.
Core utilities and types for Obsidian - A powerful Minecraft Java ↔ Bedrock Edition protocol bridge.
This package provides low-level utilities for encoding/decoding Minecraft protocol data types, cryptographic functions, and universal types used across both Java and Bedrock editions.
🚀 Features
- ✅ VarInt/VarLong - Variable-length integer encoding/decoding
- ✅ UUID - Generation, parsing, and offline UUID support
- ✅ Buffer Helpers - Read/write all Minecraft data types
- ✅ MCString - Minecraft string format (VarInt length + UTF-8)
- ✅ Cryptography - SHA-1, SHA-256, AES encryption
- ✅ Vector3 - Complete 3D vector mathematics
- ✅ Zero Dependencies - Only Node.js built-ins
- ✅ 100% Test Coverage - Comprehensive test suite
📦 Installation
npm install @obsidian-bridge/core📚 API Documentation
VarInt
Variable-length integer encoding used in Minecraft Java Edition protocol. Encodes 32-bit integers using 1-5 bytes.
const { VarInt } = require("@obsidian-bridge/core");
// Encode
const encoded = VarInt.encode(300);
console.log(encoded); // <Buffer ac 02>
// Decode
const { value, bytesRead } = VarInt.decode(encoded);
console.log(value); // 300
console.log(bytesRead); // 2
// Get length without encoding
console.log(VarInt.getLength(300)); // 2Methods:
VarInt.encode(value: number): Buffer- Encodes a 32-bit integer to VarInt format (-2147483648 to 2147483647)VarInt.decode(buffer: Buffer, offset?: number): {value: number, bytesRead: number}- Decodes VarInt from bufferVarInt.getLength(value: number): number- Calculates the byte length of encoded VarInt
Error handling:
- Throws
RangeErrorif value is out of int32 range - Throws
Errorif VarInt extends beyond buffer or is too large (>5 bytes)
VarLong
Variable-length long encoding using zigzag encoding. Encodes 64-bit BigInt values using 1-10 bytes.
const { VarLong } = require("@obsidian-bridge/core");
// Encode
const encoded = VarLong.encode(123456789n);
console.log(encoded);
// Decode
const { value, bytesRead } = VarLong.decode(encoded);
console.log(value); // 123456789n (BigInt)
// Works with numbers too
const encoded2 = VarLong.encode(12345);
// Get length
console.log(VarLong.getLength(123456789n)); // Number of bytesMethods:
VarLong.encode(value: bigint | number): Buffer- Encodes BigInt to VarLong with zigzag encodingVarLong.decode(buffer: Buffer, offset?: number): {value: bigint, bytesRead: number}- Decodes VarLong from bufferVarLong.getLength(value: bigint | number): number- Calculates the byte length
Zigzag encoding: Efficiently encodes signed integers by mapping negative values to positive ones:
0→0,1→2,-1→1,2→4,-2→3, etc.
UUID
UUID utilities for generating, validating, and converting UUIDs in Minecraft format.
const { UUID } = require("@obsidian-bridge/core");
// Generate random UUID v4
const uuid = UUID.generate();
console.log(uuid); // "550e8400-e29b-41d4-a716-446655440000"
// Validate UUID
console.log(UUID.isValid(uuid)); // true
console.log(UUID.isValid("invalid")); // false
// Convert to buffer (16 bytes)
const buffer = UUID.toBuffer(uuid);
console.log(buffer.length); // 16
// Convert back from buffer
const restored = UUID.fromBuffer(buffer);
console.log(restored === uuid); // true
// Generate offline UUID (for offline-mode servers)
const offlineUuid = UUID.offlineUUID("Notch");
console.log(offlineUuid); // "069a79f4-44e9-4726-a5be-fca90e38aaf5"
// Always generates the same UUID for the same usernameMethods:
UUID.generate(): string- Generates random UUID v4UUID.isValid(uuid: string): boolean- Validates UUID formatUUID.toBuffer(uuid: string): Buffer- Converts UUID string to 16-byte bufferUUID.fromBuffer(buffer: Buffer): string- Converts 16-byte buffer to UUID stringUUID.offlineUUID(username: string): string- Generates deterministic offline-mode UUID
Notes:
- Offline UUIDs are generated using MD5 hash of "OfflinePlayer:{username}"
- Case-sensitive for offline UUIDs
- Accepts both uppercase and lowercase UUID strings
MCString
Minecraft string format encoder/decoder. Format: VarInt(length) + UTF-8 bytes.
const { MCString } = require("@obsidian-bridge/core");
// Encode
const encoded = MCString.encode("Hello World!");
// First bytes are VarInt length, followed by UTF-8 string
// Decode
const { value, bytesRead } = MCString.decode(encoded);
console.log(value); // "Hello World!"
console.log(bytesRead); // Total bytes read (VarInt + string)
// Decode with offset
const buffer = Buffer.concat([Buffer.from([0xff, 0xff]), encoded]);
const result = MCString.decode(buffer, 2);
// Custom max length (default: 32767)
const longStr = "A".repeat(1000);
const encoded2 = MCString.encode(longStr, 2000); // OK
// MCString.encode(longStr, 500); // Throws Error
// UTF-8 support (including emoji)
const utf8Str = "Hello 🎮 Cześć 日本語";
const encoded3 = MCString.encode(utf8Str);
const decoded3 = MCString.decode(encoded3);
console.log(decoded3.value); // "Hello 🎮 Cześć 日本語"Methods:
MCString.encode(str: string, maxLength?: number): Buffer- Encodes string to MC format (default max: 32767)MCString.decode(buffer: Buffer, offset?: number): {value: string, bytesRead: number}- Decodes string from buffer
Features:
- Full UTF-8 support (including emojis and special characters)
- Efficient encoding with VarInt length prefix
- Configurable maximum length
BufferHelpers
Utilities for reading and writing all Minecraft data types from/to buffers. All methods use Big Endian byte order.
const { BufferHelpers } = require("@obsidian-bridge/core");
// Write operations (all return Buffer)
BufferHelpers.writeBoolean(true); // 1 byte
BufferHelpers.writeByte(42); // 1 byte (signed: -128 to 127)
BufferHelpers.writeUByte(255); // 1 byte (unsigned: 0 to 255)
BufferHelpers.writeShort(12345); // 2 bytes (signed: -32768 to 32767)
BufferHelpers.writeUShort(65535); // 2 bytes (unsigned: 0 to 65535)
BufferHelpers.writeInt(123456789); // 4 bytes
BufferHelpers.writeUInt(4294967295); // 4 bytes
BufferHelpers.writeLong(123456789n); // 8 bytes (BigInt)
BufferHelpers.writeULong(18446744073709551615n); // 8 bytes
BufferHelpers.writeFloat(3.14159); // 4 bytes
BufferHelpers.writeDouble(2.718281828459045); // 8 bytes
// Read operations (all return {value, bytesRead})
const intBuffer = BufferHelpers.writeInt(12345);
const { value, bytesRead } = BufferHelpers.readInt(intBuffer, 0);
console.log(value); // 12345
console.log(bytesRead); // 4
// Example: Building a packet
const packet = Buffer.concat([
BufferHelpers.writeInt(12345),
BufferHelpers.writeFloat(3.14159),
BufferHelpers.writeBoolean(true),
BufferHelpers.writeDouble(2.71828),
]);
// Example: Reading a packet
let offset = 0;
const { value: intVal, bytesRead: intBytes } = BufferHelpers.readInt(
packet,
offset,
);
offset += intBytes;
const { value: floatVal, bytesRead: floatBytes } = BufferHelpers.readFloat(
packet,
offset,
);
offset += floatBytes;
const { value: boolVal, bytesRead: boolBytes } = BufferHelpers.readBoolean(
packet,
offset,
);
offset += boolBytes;
// Utility methods
BufferHelpers.concat(buf1, buf2, buf3); // Concatenate multiple buffers
BufferHelpers.clone(buffer); // Clone a bufferAvailable Types:
- Boolean (1 byte):
true= 1,false= 0 - Byte (1 byte, signed): -128 to 127
- UByte (1 byte, unsigned): 0 to 255
- Short (2 bytes, signed): -32768 to 32767
- UShort (2 bytes, unsigned): 0 to 65535
- Int (4 bytes, signed): -2147483648 to 2147483647
- UInt (4 bytes, unsigned): 0 to 4294967295
- Long (8 bytes, BigInt): -9223372036854775808n to 9223372036854775807n
- ULong (8 bytes, BigInt): 0n to 18446744073709551615n
- Float (4 bytes): IEEE 754 single precision
- Double (8 bytes): IEEE 754 double precision
All methods:
- Read:
read<Type>(buffer: Buffer, offset?: number): {value: any, bytesRead: number} - Write:
write<Type>(value: any): Buffer - All use Big Endian byte order (network byte order)
Crypto
Cryptographic utilities for Minecraft protocol encryption and authentication.
const { Crypto } = require("@obsidian-bridge/core");
// Generate 16-byte AES shared secret
const secret = Crypto.generateSharedSecret();
console.log(secret.length); // 16
// SHA-1 hash
const hash1 = Crypto.sha1("Hello World");
console.log(hash1); // Buffer
// SHA-256 hash
const hash256 = Crypto.sha256("Hello World");
console.log(hash256); // Buffer
// Minecraft-style SHA-1 (signed hex)
// Used for server authentication with Mojang
const mcHash = Crypto.minecraftSha1(Buffer.from("test"));
console.log(mcHash); // "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83"
// Create AES/CFB8 cipher for packet encryption
const cipher = Crypto.createCipher(secret);
const encrypted = cipher.update(Buffer.from("Hello"));
// Create AES/CFB8 decipher for packet decryption
const decipher = Crypto.createDecipher(secret);
const decrypted = decipher.update(encrypted);
console.log(decrypted.toString()); // "Hello"Methods:
Crypto.generateSharedSecret(): Buffer- Generates 16-byte random AES keyCrypto.sha1(data: Buffer | string): Buffer- Computes SHA-1 hashCrypto.sha256(data: Buffer | string): Buffer- Computes SHA-256 hashCrypto.minecraftSha1(data: Buffer): string- Minecraft-style SHA-1 (signed hex notation)Crypto.createCipher(sharedSecret: Buffer): Cipher- Creates AES-128-CFB8 cipherCrypto.createDecipher(sharedSecret: Buffer): Decipher- Creates AES-128-CFB8 decipher
Notes:
- Minecraft uses AES/CFB8 encryption for packet encryption after authentication
- The shared secret is used as both the key and IV for AES/CFB8
- minecraftSha1 returns signed hex notation (negative hashes start with
-)
Vector3
Complete 3D vector implementation with full vector algebra operations.
const { Vector3 } = require("@obsidian-bridge/core");
// Create vectors
const a = new Vector3(1, 2, 3);
const b = new Vector3(4, 5, 6);
// Basic operations
const sum = a.add(b); // Vector3(5, 7, 9)
const diff = a.subtract(b); // Vector3(-3, -3, -3)
const scaled = a.multiply(2); // Vector3(2, 4, 6)
const divided = a.divide(2); // Vector3(0.5, 1, 1.5)
// Set values
const v = new Vector3();
v.set(10, 20, 30); // Returns this (chainable)
// Length (magnitude)
const len = a.length(); // Math.sqrt(1² + 2² + 3²) = 3.742
const lenSq = a.lengthSquared(); // 14 (faster, no sqrt)
// Distance
const distance = a.distanceTo(b); // 5.196
const distSq = a.distanceToSquared(b); // 27 (faster)
// Normalize (unit vector)
const normalized = a.normalize(); // Length = 1.0
console.log(normalized.length()); // 1.0
// Dot product
const dot = a.dot(b); // 1*4 + 2*5 + 3*6 = 32
// Cross product (perpendicular vector)
const cross = a.cross(b);
console.log(cross.dot(a)); // ~0 (perpendicular)
console.log(cross.dot(b)); // ~0 (perpendicular)
// Clone
const clone = a.clone(); // Independent copy
clone.set(0, 0, 0);
console.log(a); // Still Vector3(1, 2, 3)
// Serialization
const json = a.toJSON(); // {x: 1, y: 2, z: 3}
const restored = Vector3.fromJSON(json);
// String representation
console.log(a.toString()); // "Vector3(1.00, 2.00, 3.00)"Constructor:
new Vector3(x?: number, y?: number, z?: number)- Creates vector (default: 0, 0, 0)
Methods:
set(x, y, z): this- Sets values (chainable)add(other: Vector3): Vector3- Returns new vector (a + b)subtract(other: Vector3): Vector3- Returns new vector (a - b)multiply(scalar: number): Vector3- Returns new vector (scalar multiplication)divide(scalar: number): Vector3- Returns new vector (scalar division)length(): number- Returns magnitude (√(x² + y² + z²))lengthSquared(): number- Returns squared magnitude (faster, no sqrt)distanceTo(other: Vector3): number- Returns distance to other vectordistanceToSquared(other: Vector3): number- Returns squared distance (faster)normalize(): Vector3- Returns unit vector (length = 1)dot(other: Vector3): number- Returns dot product (a·b)cross(other: Vector3): Vector3- Returns cross product (a × b)clone(): Vector3- Returns independent copytoJSON(): {x, y, z}- Serializes to JSONstatic fromJSON(json): Vector3- Deserializes from JSONtoString(): string- Returns string representation
Features:
- All operations return new vectors (immutable)
- Optimized with squared methods to avoid sqrt when possible
- Full 3D vector algebra support
- Useful for positions, velocities, directions, normals, etc.
🧪 Testing
This package has comprehensive test coverage for all modules.
# Run tests
npm test
# Watch mode
npm run test:watch
# Coverage report
npm run test:coverageTest files:
tests/utils/varint.test.js- VarInt encoding/decodingtests/utils/VarLong.test.js- VarLong encoding/decodingtests/utils/UUID.test.js- UUID utilitiestests/utils/MCString.test.js- String encoding/decodingtests/utils/BufferHelpers.test.js- All buffer read/write operationstests/types/Vector3.test.js- Vector3 algebra operations
🔧 Usage Examples
Complete Packet Example
const {
VarInt,
MCString,
BufferHelpers,
UUID,
} = require("@obsidian-bridge/core");
// Building a login packet
const username = "Steve";
const playerUuid = UUID.offlineUUID(username);
const packet = Buffer.concat([
VarInt.encode(0x00), // Packet ID
MCString.encode(username), // Username
UUID.toBuffer(playerUuid), // UUID
]);
console.log(`Packet size: ${packet.length} bytes`);
// Parsing the packet
let offset = 0;
const { value: packetId, bytesRead: idBytes } = VarInt.decode(packet, offset);
offset += idBytes;
const { value: parsedUsername, bytesRead: nameBytes } = MCString.decode(
packet,
offset,
);
offset += nameBytes;
const uuidBuffer = packet.slice(offset, offset + 16);
const parsedUuid = UUID.fromBuffer(uuidBuffer);
console.log(`Packet ID: ${packetId}`);
console.log(`Username: ${parsedUsername}`);
console.log(`UUID: ${parsedUuid}`);Encryption Example
const { Crypto, VarInt, BufferHelpers } = require("@obsidian-bridge/core");
// Generate shared secret
const sharedSecret = Crypto.generateSharedSecret();
// Hash for server authentication
const serverHash = Crypto.minecraftSha1(
Buffer.concat([
Buffer.from(""), // Server ID (empty for online mode)
sharedSecret,
Buffer.from("public_key_here"), // Server's public key
]),
);
console.log(`Server hash: ${serverHash}`);
// Create cipher/decipher for packet encryption
const cipher = Crypto.createCipher(sharedSecret);
const decipher = Crypto.createDecipher(sharedSecret);
// Encrypt packet
const originalPacket = Buffer.concat([
VarInt.encode(0x01),
BufferHelpers.writeInt(123),
]);
const encrypted = cipher.update(originalPacket);
const decrypted = decipher.update(encrypted);
console.log("Original:", originalPacket);
console.log("Encrypted:", encrypted);
console.log("Decrypted:", decrypted);
console.log("Match:", originalPacket.equals(decrypted)); // truePosition & Movement Example
const { Vector3, BufferHelpers } = require("@obsidian-bridge/core");
// Player position
const playerPos = new Vector3(100.5, 64.0, 200.3);
const targetPos = new Vector3(150.0, 70.0, 250.0);
// Calculate movement
const direction = targetPos.subtract(playerPos);
const distance = playerPos.distanceTo(targetPos);
const moveDir = direction.normalize(); // Unit vector
console.log(`Distance: ${distance.toFixed(2)} blocks`);
console.log(`Direction: ${moveDir.toString()}`);
// Velocity (5 blocks per second toward target)
const velocity = moveDir.multiply(5);
// Simulate movement (1 tick = 0.05 seconds)
const newPos = playerPos.add(velocity.multiply(0.05));
console.log(`New position: ${newPos.toString()}`);
// Encode position for network
const positionPacket = Buffer.concat([
BufferHelpers.writeDouble(newPos.x),
BufferHelpers.writeDouble(newPos.y),
BufferHelpers.writeDouble(newPos.z),
]);🤝 Contributing
Contributions are welcome! Please read our Contributing Guide.
📄 License
MIT © xwzcc
🔗 Related Packages
- @obsidian/java-protocol - Minecraft Java Edition protocol
- @obsidian/bedrock-protocol - Minecraft Bedrock Edition protocol
- @obsidian/data-mappings - Data mappings between protocols
- @obsidian/protocol-translator - Protocol translation layer
- @obsidian/bridge-server - Complete bridge server
