[repl] remote tcp repl
This commit is contained in:
parent
8a18be2d6a
commit
6ca0e1166c
21
README.md
21
README.md
@ -31,21 +31,31 @@ npx patch-package
|
|||||||
运行下面命令启动:
|
运行下面命令启动:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
node index.mjs profile@host:port
|
node index.mjs profile@hostname[:port]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
参数含义:
|
||||||
|
|
||||||
|
+ `profile`:表示连接到服务器使用的角色名
|
||||||
|
+ `hostname`:服务器地址
|
||||||
|
+ `port`:服务器端口(可选,默认值为 25565)
|
||||||
|
|
||||||
命令行选项:
|
命令行选项:
|
||||||
|
|
||||||
+ `--offline`:使用离线模式启动
|
+ `--offline`:使用离线模式启动
|
||||||
+ `--credentials-lib <file>`:指定登录凭据文件(默认值:`credentials.json`)
|
+ `--credentials-lib <file>`:指定登录凭据文件(可选,默认值:`credentials.json`)
|
||||||
+ `--no-repl`:禁用终端内 REPL
|
+ `--no-local-repl`:禁用终端内 REPL
|
||||||
+ `--protocal`:指定服务器版本(不指定时自动检测)
|
+ `--protocal`:指定服务器版本(可选,不指定时自动检测)
|
||||||
+ `--owner`: 指定 REPL 上下文内 `owner()` 函数返回的玩家
|
+ `--owner`: 指定 REPL 上下文内 `owner()` 函数返回的玩家
|
||||||
|
+ `--enable-tcp-repl`:启用基于 TCP 连接的远程 REPL
|
||||||
|
* `--tcp-repl-port`:TCP 远程 REPL 的服务端口(可选,默认值 2121)
|
||||||
|
+ `--remote-repl-passcode-length`:远程 REPL 的服务口令强度(可选,默认值 8)
|
||||||
|
|
||||||
## REPL
|
## REPL
|
||||||
|
|
||||||
REPL 上下文内预先定义了如下变量/函数/类型:
|
REPL 上下文内预先定义了如下变量/函数/类型:
|
||||||
|
|
||||||
|
+ `PI`:常数,等于 `Math.PI`
|
||||||
+ `bot`:机器人对象
|
+ `bot`:机器人对象
|
||||||
+ `Vec3`:3 维向量类型
|
+ `Vec3`:3 维向量类型
|
||||||
+ `debug`:调试日志模块
|
+ `debug`:调试日志模块
|
||||||
@ -57,4 +67,5 @@ REPL 上下文内预先定义了如下变量/函数/类型:
|
|||||||
+ `sc.debug_mfc()`:缩写,等价于 `debug.enable('mineflayer-control')`
|
+ `sc.debug_mfc()`:缩写,等价于 `debug.enable('mineflayer-control')`
|
||||||
+ `sc.debug_mff()`:缩写,等价于 `debug.enable('mineflayer-fly-control')`
|
+ `sc.debug_mff()`:缩写,等价于 `debug.enable('mineflayer-fly-control')`
|
||||||
+ `sc.sleep`:缩写,等价于 `lib.utils.asyncSleep`
|
+ `sc.sleep`:缩写,等价于 `lib.utils.asyncSleep`
|
||||||
+ `sc.tossHeld`:缩写,等价于 `bot.tossStack(bot.heldItem)`
|
+ `sc.tossHeld()`:缩写,等价于 `bot.tossStack(bot.heldItem)`
|
||||||
|
+ `bb`:所有 REPL 实例的共享数据
|
||||||
|
48
index.mjs
48
index.mjs
@ -5,6 +5,8 @@ import { asyncSleep, parseLogin, waitEvent } from 'compass-utils';
|
|||||||
import repl from 'node:repl';
|
import repl from 'node:repl';
|
||||||
import 'enhanced-vec3';
|
import 'enhanced-vec3';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import { createLocalRepl, createTcpReplServer } from './repl/index.mjs';
|
||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const args = yargs((await import('yargs/helpers')).hideBin(process.argv))
|
const args = yargs((await import('yargs/helpers')).hideBin(process.argv))
|
||||||
@ -16,8 +18,8 @@ async function main() {
|
|||||||
description: 'Bot\'s owner name.',
|
description: 'Bot\'s owner name.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
requiresArg: false,
|
requiresArg: false,
|
||||||
}).option('no-repl', {
|
}).option('no-local-repl', {
|
||||||
description: 'Disable bot REPL control.',
|
description: 'Disable bot REPL control in current stdin/stdout.',
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
}).option('credentials-lib', {
|
}).option('credentials-lib', {
|
||||||
description: 'Credentials\' library file.',
|
description: 'Credentials\' library file.',
|
||||||
@ -26,11 +28,22 @@ async function main() {
|
|||||||
}).option('offline', {
|
}).option('offline', {
|
||||||
description: 'Login without credentials.',
|
description: 'Login without credentials.',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
}).usage('Uasge: profile@host:port').help().alias('help', 'h').argv;
|
}).option('enable-tcp-repl', {
|
||||||
|
description: 'Enable bot REPL control as a TCP service.',
|
||||||
|
type: 'boolean',
|
||||||
|
}).option('tcp-repl-port', {
|
||||||
|
description: 'Telnet REPL service port.',
|
||||||
|
type: 'number',
|
||||||
|
default: 2121,
|
||||||
|
}).option('remote-repl-passcode-length', {
|
||||||
|
description: 'Length of remote REPL passcode in bytes',
|
||||||
|
type: 'number',
|
||||||
|
default: 8,
|
||||||
|
}).usage('Uasge: profile@hostname[:port]').help().alias('help', 'h').argv;
|
||||||
|
|
||||||
let login_info = args._[0];
|
let login_info = args._[0];
|
||||||
if (login_info == null) { return; }
|
if (login_info == null) { return; }
|
||||||
const [name, host, port] = parseLogin(login_info);
|
const [name, hostname, port] = parseLogin(login_info);
|
||||||
|
|
||||||
let session, endpoint = null;
|
let session, endpoint = null;
|
||||||
if (args.offline) {
|
if (args.offline) {
|
||||||
@ -45,7 +58,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bot = mineflayer.createBot({
|
const bot = mineflayer.createBot({
|
||||||
host, port, version: args.protocal,
|
host: hostname, port, version: args.protocal,
|
||||||
...session.mineflayer(endpoint)
|
...session.mineflayer(endpoint)
|
||||||
});
|
});
|
||||||
bot.on('error', console.error);
|
bot.on('error', console.error);
|
||||||
@ -61,6 +74,7 @@ async function main() {
|
|||||||
bot.loadPlugin((await import('mineflayer-fly-control')).default);
|
bot.loadPlugin((await import('mineflayer-fly-control')).default);
|
||||||
await bot.waitEvent('spawn');
|
await bot.waitEvent('spawn');
|
||||||
|
|
||||||
|
const context_shared = {};
|
||||||
async function loadReplContextModules(context) {
|
async function loadReplContextModules(context) {
|
||||||
context.lib = {
|
context.lib = {
|
||||||
utils: await import('compass-utils'),
|
utils: await import('compass-utils'),
|
||||||
@ -75,7 +89,9 @@ async function main() {
|
|||||||
return bot.players[args.owner];
|
return bot.players[args.owner];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
context.PI = Math.PI;
|
||||||
context.debug = debug;
|
context.debug = debug;
|
||||||
|
context.bb = context_shared;
|
||||||
context.sc = {};
|
context.sc = {};
|
||||||
context.sc.pos = () => bot.entity.position;
|
context.sc.pos = () => bot.entity.position;
|
||||||
context.sc.debug_mfc = () => debug.enable('mineflayer-control');
|
context.sc.debug_mfc = () => debug.enable('mineflayer-control');
|
||||||
@ -84,22 +100,20 @@ async function main() {
|
|||||||
context.sc.tossHeld = () => bot.tossStack(bot.heldItem);
|
context.sc.tossHeld = () => bot.tossStack(bot.heldItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args.noRepl) {
|
if (args.enableTcpRepl) {
|
||||||
let r = repl.start({
|
const passcode = randomBytes(args.remoteReplPasscodeLength);
|
||||||
prompt: 'local > ',
|
console.log('Remote REPL Passcode:', passcode.toString('hex'));
|
||||||
input: process.stdin,
|
let server = createTcpReplServer(args.tcpReplPort, passcode, loadReplContextModules);
|
||||||
output: process.stdout,
|
process.on('exit', () => server.close());
|
||||||
color: true,
|
}
|
||||||
terminal: true,
|
|
||||||
ignoreUndefined: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
r.on('exit', () => bot.quit());
|
if (!args.noLocalRepl) {
|
||||||
loadReplContextModules(r.context);
|
let repl_local = createLocalRepl(loadReplContextModules);
|
||||||
|
repl_local.on('exit', () => bot.quit());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(err => {
|
main().catch(err => {
|
||||||
console.error('Error: ', err);
|
console.error(`Error: ${err}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
85
repl/index.mjs
Normal file
85
repl/index.mjs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import repl from 'node:repl';
|
||||||
|
import net from 'node:net';
|
||||||
|
import { Buffer } from 'node:buffer';
|
||||||
|
import { timingSafeEqual } from 'node:crypto';
|
||||||
|
import { waitEvent } from 'compass-utils';
|
||||||
|
import { RemoteTTY } from './remote-tty.mjs';
|
||||||
|
|
||||||
|
import debug from 'debug';
|
||||||
|
const logger = debug('compass-repl');
|
||||||
|
|
||||||
|
export function createRepl(istream, ostream, prompt, contextLoader) {
|
||||||
|
let r = repl.start({
|
||||||
|
prompt: `${prompt} > `,
|
||||||
|
input: istream,
|
||||||
|
output: ostream,
|
||||||
|
color: true,
|
||||||
|
terminal: true,
|
||||||
|
ignoreUndefined: true,
|
||||||
|
});
|
||||||
|
r.on('SIGCONT', () => { });
|
||||||
|
r.on('SIGTSTP', () => {});
|
||||||
|
contextLoader(r.context);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createLocalRepl(contextLoader) {
|
||||||
|
return createRepl(process.stdin, process.stdout, 'local', contextLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTcpReplServer(port, passcode, contextLoader) {
|
||||||
|
const connect_repl_sockets = new Set();
|
||||||
|
let server = net.createServer(socket => {
|
||||||
|
logger('New TCP connection');
|
||||||
|
function verifyPasscode(data) {
|
||||||
|
let pdata = Buffer.from(data);
|
||||||
|
logger(`Got passcode: ${pdata.toString('hex')}`);
|
||||||
|
let flag = true;
|
||||||
|
if (pdata.length != passcode.length) {
|
||||||
|
flag = false;
|
||||||
|
pdata = passcode;
|
||||||
|
}
|
||||||
|
if (timingSafeEqual(passcode, pdata) && flag) {
|
||||||
|
logger('Passcode correct, 0x21!');
|
||||||
|
socket.write(Buffer.from([0x21]));
|
||||||
|
let tty = new RemoteTTY({}, socket, socket);
|
||||||
|
tty.ttyReady().then(() => {
|
||||||
|
logger('Remote TTY is ready, starting REPL');
|
||||||
|
let r = createRepl(tty, tty, 'tcp', contextLoader);
|
||||||
|
r.on('exit', () => {
|
||||||
|
connect_repl_sockets.delete(socket);
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
connect_repl_sockets.add(socket);
|
||||||
|
}).catch(err => {
|
||||||
|
logger(`TTY Ready Error: ${err}`);
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger('Passcode incorrect, 0x20!');
|
||||||
|
socket.write(Buffer.from([0x20]));
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket.once('data', (data) => verifyPasscode(data));
|
||||||
|
|
||||||
|
socket.on('close', () => {
|
||||||
|
logger('Disconnected');
|
||||||
|
connect_repl_sockets.delete(socket);
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
logger(`Remote Connect Error: ${err}`);
|
||||||
|
connect_repl_sockets.delete(socket);
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.listen(port);
|
||||||
|
process.on('exit', () => {
|
||||||
|
for (const socket of connect_repl_sockets) {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return server;
|
||||||
|
}
|
197
repl/remote-tty.mjs
Normal file
197
repl/remote-tty.mjs
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import { waitEvent } from 'compass-utils';
|
||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
|
import { Duplex } from 'node:stream';
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
|
||||||
|
import debug from 'debug';
|
||||||
|
const logger = debug('compass-repl:RemoteTTY');
|
||||||
|
|
||||||
|
const kSource = Symbol('kSource');
|
||||||
|
|
||||||
|
export class RemoteTTY extends Duplex {
|
||||||
|
#rows
|
||||||
|
#cols
|
||||||
|
#color_depths
|
||||||
|
#tty_ready
|
||||||
|
#tty_ready_resolve
|
||||||
|
#event_callbacks
|
||||||
|
constructor(options, input, output) {
|
||||||
|
super(options);
|
||||||
|
this[kSource] = {
|
||||||
|
input: input,
|
||||||
|
output: output,
|
||||||
|
};
|
||||||
|
this.#tty_ready = new Promise(resolve => {
|
||||||
|
this.#tty_ready_resolve = resolve;
|
||||||
|
});
|
||||||
|
this.#event_callbacks = new Map();
|
||||||
|
this[kSource].input.on('close', () => this.push(null));
|
||||||
|
this[kSource].input.on('data', (data) => {
|
||||||
|
try {
|
||||||
|
if (!(data instanceof Buffer)) {
|
||||||
|
data = Buffer.from(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[0] === 0x02) {
|
||||||
|
if (!this.push(data.slice(1))) {
|
||||||
|
this[kSource].input.pause();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[0] === 0x01) {
|
||||||
|
// Initialize data
|
||||||
|
this.#rows = data.readUInt16BE(1);
|
||||||
|
this.#cols = data.readUInt16BE(3);
|
||||||
|
this.#color_depths = data.readUInt8(5);
|
||||||
|
if (this.#tty_ready_resolve) {
|
||||||
|
this.#tty_ready_resolve();
|
||||||
|
this.#tty_ready_resolve = null;
|
||||||
|
}
|
||||||
|
} else if (data[0] == 0x03) {
|
||||||
|
// Resize event
|
||||||
|
this.#rows = data.readUInt16BE(1);
|
||||||
|
this.#cols = data.readUInt16BE(3);
|
||||||
|
this.emit('resize');
|
||||||
|
} else if (data[0] == 0x04) {
|
||||||
|
// Callback
|
||||||
|
let id = (data.readBigUInt64BE(1) << 64n) + data.readBigUInt64BE(9);
|
||||||
|
if (this.#event_callbacks.has(id)) {
|
||||||
|
logger(`Rescived callback ${data.toString('hex')}`);
|
||||||
|
this.#event_callbacks.get(id)();
|
||||||
|
this.#event_callbacks.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get rows() {
|
||||||
|
return this.#rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
get columns() {
|
||||||
|
return this.#cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWindowSize() {
|
||||||
|
return [this.#rows, this.#cols];
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorDepth() {
|
||||||
|
return this.#color_depths;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasColors(count = 16) {
|
||||||
|
return (1 << this.#color_depths) >= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#registerCallback(data, callback) {
|
||||||
|
let cbid = randomBytes(16);
|
||||||
|
let cbid_v = (cbid.readBigUInt64BE(0) << 64n) + cbid.readBigUInt64BE(8);
|
||||||
|
if (callback) {
|
||||||
|
logger(`Registered callback ${cbid.toString('hex')}`);
|
||||||
|
this.#event_callbacks.set(cbid_v, callback);
|
||||||
|
}
|
||||||
|
cbid.copy(data, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLine(dir, callback) {
|
||||||
|
logger('clearLine');
|
||||||
|
let data = Buffer.alloc(18);
|
||||||
|
data[0] = 0x10;
|
||||||
|
data.writeInt8(dir, 17);
|
||||||
|
this.#registerCallback(data, callback);
|
||||||
|
this.#sendData(data);
|
||||||
|
return this.writableNeedDrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearScreenDown(callback) {
|
||||||
|
logger('clearScreenDown');
|
||||||
|
let data = Buffer.alloc(17);
|
||||||
|
data[0] = 0x11;
|
||||||
|
this.#registerCallback(data, callback);
|
||||||
|
this.#sendData(data);
|
||||||
|
return this.writableNeedDrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorTo(x, y, callback) {
|
||||||
|
logger('cursorTo');
|
||||||
|
let data = Buffer.alloc(22);
|
||||||
|
if (typeof y !== 'number') {
|
||||||
|
callback = y;
|
||||||
|
y = 0;
|
||||||
|
data[17] = 0x01;
|
||||||
|
} else {
|
||||||
|
data[17] = 0x00;
|
||||||
|
}
|
||||||
|
data[0] = 0x12;
|
||||||
|
data.writeUInt16BE(x, 18);
|
||||||
|
data.writeUInt16BE(y, 20);
|
||||||
|
|
||||||
|
this.#registerCallback(data, callback);
|
||||||
|
this.#sendData(data);
|
||||||
|
return this.writableNeedDrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveCursor(dx, dy, callback) {
|
||||||
|
logger('moveCursor');
|
||||||
|
let data = Buffer.alloc(21);
|
||||||
|
data[0] = 0x13;
|
||||||
|
data.writeUInt16BE(dx, 17);
|
||||||
|
data.writeUInt16BE(dy, 19);
|
||||||
|
|
||||||
|
this.#registerCallback(data, callback);
|
||||||
|
this.#sendData(data);
|
||||||
|
return this.writableNeedDrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #sendData(data) {
|
||||||
|
if (this[kSource].output.writableNeedDrain) {
|
||||||
|
await waitEvent(this[kSource].output, 'darin');
|
||||||
|
}
|
||||||
|
const writePromise = promisify((data, callback) =>
|
||||||
|
this[kSource].output.write(data, 'utf-8', callback));
|
||||||
|
await writePromise(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_construct(callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
_write(chunk, encoding, callback) {
|
||||||
|
if (!(chunk instanceof Buffer)) {
|
||||||
|
chunk = Buffer.from(chunk, encoding);
|
||||||
|
}
|
||||||
|
chunk = Buffer.concat([Buffer.from([0x02]), chunk]);
|
||||||
|
this.#sendData(chunk).then(() => callback(null)).catch(err => callback(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
_destroy(err, callback) {
|
||||||
|
this[kSource].input.destory(err);
|
||||||
|
this[kSource].output.destory(err);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
_read(_size) {
|
||||||
|
this[kSource].input.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
setRawMode() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRaw() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTTY() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ttyReady() {
|
||||||
|
return this.#tty_ready;
|
||||||
|
}
|
||||||
|
};
|
161
scripts/tcp-repl-client.mjs
Normal file
161
scripts/tcp-repl-client.mjs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { Readable, Writable } from 'node:stream';
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
import net from 'node:net';
|
||||||
|
import { waitEvent } from 'compass-utils';
|
||||||
|
import readline from 'node:readline';
|
||||||
|
import yargs from 'yargs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Readable} input
|
||||||
|
* @param {Writable} output
|
||||||
|
*/
|
||||||
|
function ttyServer(input, output) {
|
||||||
|
const stdin = process.stdin;
|
||||||
|
const stdout = process.stdout;
|
||||||
|
stdin.setRawMode(true);
|
||||||
|
|
||||||
|
const writePromise = promisify((data, callback) => output.write(data, 'utf-8', callback));
|
||||||
|
async function sendData(data) {
|
||||||
|
if (!output.writable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (output.writableNeedDrain) {
|
||||||
|
await waitEvent(output, 'darin');
|
||||||
|
}
|
||||||
|
await writePromise(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdin.on('data', async (chunk) => {
|
||||||
|
if (chunk.length === 1 && chunk[0] === 4) {
|
||||||
|
stdin.emit('end');
|
||||||
|
}
|
||||||
|
chunk = Buffer.concat([Buffer.from([0x02]), chunk]);
|
||||||
|
await sendData(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
stdin.on('close', () => socket.destroy());
|
||||||
|
|
||||||
|
let init_data = Buffer.alloc(6);
|
||||||
|
init_data[0] = 0x01;
|
||||||
|
init_data.writeUInt16BE(stdout.rows, 1);
|
||||||
|
init_data.writeUInt16BE(stdout.columns, 3);
|
||||||
|
init_data.writeUInt8(stdout.getColorDepth(), 5);
|
||||||
|
sendData(init_data);
|
||||||
|
|
||||||
|
stdout.on('resize', () => {
|
||||||
|
let resize_data = Buffer.alloc(5);
|
||||||
|
resize_data[0] = 0x03;
|
||||||
|
resize_data.writeUInt16BE(stdout.rows, 1);
|
||||||
|
resize_data.writeUInt16BE(stdout.columns, 3);
|
||||||
|
sendData(resize_data);
|
||||||
|
});
|
||||||
|
|
||||||
|
stdout.on('drain', () => input.resume());
|
||||||
|
input.on('end', () => {
|
||||||
|
console.log('\nDisconnected (Server).');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
stdin.on('end', () => {
|
||||||
|
input.destroy();
|
||||||
|
output.destroy();
|
||||||
|
console.log('\nDisconnected (Client).');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
input.on('data', (data) => {
|
||||||
|
if (!(data instanceof Buffer)) {
|
||||||
|
data = Buffer.from(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[0] === 0x02) {
|
||||||
|
if (!stdout.write(data)) { input.pause(); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[0] == 0x10) {
|
||||||
|
// clearLine
|
||||||
|
let res = Buffer.alloc(17);
|
||||||
|
data.copy(res, 1, 1, 17);
|
||||||
|
res[0] = 0x04;
|
||||||
|
let dir = data.readInt8(17);
|
||||||
|
stdout.clearLine(dir, () => {
|
||||||
|
sendData(res);
|
||||||
|
});
|
||||||
|
} else if (data[0] == 0x11) {
|
||||||
|
// clearScreenDown
|
||||||
|
let res = Buffer.alloc(17);
|
||||||
|
data.copy(res, 1, 1, 17);
|
||||||
|
res[0] = 0x04;
|
||||||
|
stdout.clearScreenDown(() => {
|
||||||
|
sendData(res);
|
||||||
|
});
|
||||||
|
} else if (data[0] == 0x12) {
|
||||||
|
// cursorTo
|
||||||
|
let res = Buffer.alloc(17);
|
||||||
|
data.copy(res, 1, 1, 17);
|
||||||
|
res[0] = 0x04;
|
||||||
|
let flag = data[17];
|
||||||
|
let x = data.readUInt16BE(18);
|
||||||
|
let y = data.readUInt16BE(20);
|
||||||
|
if (flag) {
|
||||||
|
stdout.cursorTo(x, () => {
|
||||||
|
sendData(res);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
stdout.cursorTo(x, y, () => {
|
||||||
|
sendData(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (data[0] == 0x13) {
|
||||||
|
// moveCursor
|
||||||
|
let res = Buffer.alloc(17);
|
||||||
|
data.copy(res, 1, 1, 17);
|
||||||
|
res[0] = 0x04;
|
||||||
|
let dx = data.readUInt16BE(17);
|
||||||
|
let dy = data.readUInt16BE(19);
|
||||||
|
stdout.moveCursor(dx, dy, () => {
|
||||||
|
sendData(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const args = yargs((await import('yargs/helpers')).hideBin(process.argv))
|
||||||
|
.option('port', {
|
||||||
|
description: 'Remote TCP REPL service port.',
|
||||||
|
default: 2121,
|
||||||
|
}).usage('Uasge: hostname').help().alias('help', 'h').argv;
|
||||||
|
|
||||||
|
let socket = net.connect(args.port, args._[0]);
|
||||||
|
await waitEvent(socket, 'connect');
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
const question = promisify(rl.question).bind(rl);
|
||||||
|
const passcode_string = (await question('Passcode: ')).trim();
|
||||||
|
const passcode = Buffer.from(passcode_string, 'hex');
|
||||||
|
if (passcode.length == 0) {
|
||||||
|
console.log('A passcode is required.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
const sendPasscode = promisify((cb) => socket.write(passcode, cb));
|
||||||
|
await sendPasscode();
|
||||||
|
let res = await waitEvent(socket, 'data');
|
||||||
|
if (res[0] == 0x20) {
|
||||||
|
console.log('Passcode incorrect.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('Connected.');
|
||||||
|
process.stdin.resume();
|
||||||
|
ttyServer(socket, socket);
|
||||||
|
console.log(process.stdin.isRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error(`Error: ${err}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
@ -30,15 +30,23 @@ export function waitEvent(em, event) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function asyncSleep(t) {
|
/**
|
||||||
|
* @param {number} timeout
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export function asyncSleep(timeout) {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
setTimeout(resolve, t);
|
setTimeout(resolve, timeout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function asyncTimeout(t) {
|
/**
|
||||||
|
* @param {number} timeout
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export function asyncTimeout(timeout) {
|
||||||
return new Promise((_resolve, reject) => {
|
return new Promise((_resolve, reject) => {
|
||||||
setTimeout(reject, t);
|
setTimeout(reject, timeout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user