UNPKG

jsp-raknet

Version:

Basic RakNet implementation written in Javascript

206 lines (205 loc) 8.65 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = void 0; const dgram_1 = __importDefault(require("dgram")); const crypto_1 = __importDefault(require("crypto")); const events_1 = __importDefault(require("events")); const Connection_1 = require("./Connection"); const InetAddress_1 = __importDefault(require("./utils/InetAddress")); const OpenConnectionRequest1_1 = __importDefault(require("./protocol/OpenConnectionRequest1")); const UnconnectedPong_1 = __importDefault(require("./protocol/UnconnectedPong")); const UnconnectedPing_1 = __importDefault(require("./protocol/UnconnectedPing")); const OpenConnectionReply1_1 = __importDefault(require("./protocol/OpenConnectionReply1")); const OpenConnectionRequest2_1 = __importDefault(require("./protocol/OpenConnectionRequest2")); const OpenConnectionReply2_1 = __importDefault(require("./protocol/OpenConnectionReply2")); const Identifiers_1 = __importDefault(require("./protocol/Identifiers")); const debug = require('debug')('raknet'); // RakNet protocol versions const RAKNET_PROTOCOL = 10; const RAKNET_TPS = 100; const RAKNET_TICK_LENGTH = 1 / RAKNET_TPS; // Constantly reconnect with smaller MTU const START_MTU_SIZE = 1492; class Client extends events_1.default { constructor(hostname, port) { super(); this.client = true; this.server = false; this.mtuSize = 1400; this.state = 0 /* Waiting */; this.running = true; this.lastPong = BigInt(Date.now()); console.assert(hostname.length, 'Hostname cannot be empty'); console.assert(port, 'Port cannot be empty'); this.hostname = hostname; this.port = port; this.address = new InetAddress_1.default(this.hostname, this.port); this.socket = dgram_1.default.createSocket({ type: 'udp4', recvBufferSize: 1024 * 256 * 2, sendBufferSize: 1024 * 16 }); this.id = Buffer.from(crypto_1.default.randomBytes(8)).readBigInt64BE(); this.inLog = (...args) => debug('C -> ', hostname, ...args); this.outLog = (...args) => debug('C <- ', hostname, ...args); this.socket.on('message', (buffer, rinfo) => { this.inLog('[S->C]', buffer, rinfo); this.handle(buffer, rinfo); }); } async connect() { const MAX_CONNECTION_TRIES = 5; for (let i = 0; i < MAX_CONNECTION_TRIES; i++) { this.outLog('Connecting with mtu', this.mtuSize); this.sendConnectionRequest(); await sleep(1500); // Wait some time before sending another connection request if (this.state != 0 /* Waiting */) break; this.mtuSize -= 100; } this.startTicking(); // tick sessions } handle(buffer, rinfo) { let header = buffer.readUInt8(); // Read packet header to recognize packet type let token = `${rinfo.address}:${rinfo.port}`; // debug('[raknet] Hadling packet', buffer, this.connection) if (this.connection && buffer[0] > 0x20) { this.connection.recieve(buffer); } else { // debug('Header', header.toString(16)) switch (header) { case Identifiers_1.default.UnconnectedPong: this.handleUnconnectedPong(buffer); break; case Identifiers_1.default.OpenConnectionReply1: this.handleOpenConnectionReply1(buffer).then(buffer => { this.sendBuffer(buffer); }); break; case Identifiers_1.default.OpenConnectionReply2: this.handleOpenConnectionReply2(buffer); break; case Identifiers_1.default.NoFreeIncomingConnections: this.inLog('[raknet] Server rejected connection - full?'); this.emit('error', 'Server is full'); break; case Identifiers_1.default.ConnectionAttemptFailed: this.inLog('[raknet] Connection was rejected by server'); this.emit('error', 'Connection request rejected'); break; default: } } } sendConnectionRequest() { this.outLog('sending connection req'); const packet = new OpenConnectionRequest1_1.default(); packet.mtuSize = this.mtuSize; packet.protocol = RAKNET_PROTOCOL; packet.encode(); this.sendBuffer(packet.buffer); this.emit('connecting', { mtuSize: packet.mtuSize, protocol: RAKNET_PROTOCOL }); } handleUnconnectedPong(buffer) { this.inLog('[raknet] got unconnected pong'); const decodedPacket = new UnconnectedPong_1.default(); decodedPacket.buffer = buffer; decodedPacket.decode(); this.lastPong = BigInt(decodedPacket.sendTimestamp); this.emit('unconnectedPong', decodedPacket.serverName, decodedPacket.serverGUID, this.lastPong); } sendUnconnectedPing() { const packet = new UnconnectedPing_1.default(); packet.sendTimestamp = BigInt(Date.now()); packet.clientGUID = this.id; packet.encode(); this.sendBuffer(packet.buffer); } ping(cb) { this.sendUnconnectedPing(); this.once('unconnectedPong', (serverName, guid) => { cb(serverName, guid); }); } async handleOpenConnectionReply1(buffer) { this.inLog('[raknet] Got OpenConnectionReply1'); this.state = 1 /* Connecting */; const decodedPacket = new OpenConnectionReply1_1.default(); decodedPacket.buffer = buffer; decodedPacket.decode(); const packet = new OpenConnectionRequest2_1.default(); packet.mtuSize = Math.min(decodedPacket.mtuSize, 1400); packet.clientGUID = this.id; packet.serverAddress = this.address; // debug('MTU', decodedPacket, packet.mtuSize, packet.clientGUID, packet.serverAddress.address) packet.encode(); return packet.buffer; } async handleOpenConnectionReply2(buffer) { this.inLog('[client] Got conn reply 2'); const decodedPacket = new OpenConnectionReply2_1.default(); decodedPacket.buffer = buffer; decodedPacket.decode(); this.mtuSize = Math.min(decodedPacket.mtuSize, 1400); this.connection = new Connection_1.Connection(this, this.mtuSize, this.address); this.connection.inLog = this.inLog; this.connection.outLog = this.outLog; this.connection.sendConnectionRequest(this.id, this.mtuSize); this.state = 2 /* Connected */; } startTicking() { let ticks = 0; this.int = setInterval(() => { ticks++; if (this.running) { this.connection?.update(Date.now()); if (ticks % 100 == 0 && !globalThis.debuggingRaknet) { // TODO: How long do we wait before sending? about 1s for now this.outLog('Sending ping'); this.connection ? this.connection.sendConnectedPing() : this.sendUnconnectedPing(); let td = BigInt(Date.now()) - this.lastPong; if (td > 4000) { // 4s timeout this.inLog(td, this.lastPong); // this.close('timeout') // this.shutdown = true } } } else { clearInterval(this.int); } }, RAKNET_TICK_LENGTH * 1000); } close(reason) { if (!this.running) return; this.connection?.close(); setTimeout(() => this.socket.close(), 100); this.connection = null; this.running = false; clearInterval(this.int); this.outLog('[client] closing', reason); this.emit('disconnect', reason); this.removeAllListeners(); } removeConnection(args) { this.close(args); } /** * Send packet buffer to the server * * @param {Buffer} buffer * @param {string} address * @param {number} port */ sendBuffer(buffer, to = this.address) { this.socket.send(buffer, 0, buffer.length, to.port, to.address); } } exports.Client = Client; async function sleep(ms) { return new Promise(res => { setTimeout(() => { res(true); }, ms); }); }