diff --git a/core/door.js b/core/door.js index a155a8d8..1db8a47e 100644 --- a/core/door.js +++ b/core/door.js @@ -7,6 +7,8 @@ var events = require('events'); var _ = require('lodash'); var pty = require('ptyw.js'); var decode = require('iconv-lite').decode; +var net = require('net'); +var async = require('async'); exports.Door = Door; @@ -18,12 +20,18 @@ function Door(client, exeInfo) { this.exeInfo.encoding = this.exeInfo.encoding || 'cp437'; - // exeInfo.cmd - // exeInfo.args[] - // exeInfo.env{} - // exeInfo.cwd - // exeInfo.encoding - + // + // Members of exeInfo: + // cmd + // args[] + // env{} + // cwd + // io + // encoding + // dropFile + // node + // inhSocket + // } require('util').inherits(Door, events.EventEmitter); @@ -34,39 +42,95 @@ Door.prototype.run = function() { var self = this; - var door = pty.spawn(self.exeInfo.cmd, self.exeInfo.args, { - cols : self.client.term.termWidth, - rows : self.client.term.termHeight, - // :TODO: cwd - env : self.exeInfo.env, - //encoding : self.client.term.outputEncoding, - }); + var doorData = function(data) { + self.client.term.write(decode(data, self.exeInfo.encoding)); + }; - // :TODO: can we pause the stream, write our own "Loading...", then on resume? + var sockServer; - //door.pipe(self.client.term.output); - self.client.term.output.pipe(door); - - // :TODO: do this with pluggable pipe/filter classes + async.series( + [ + function prepareServer(callback) { + if('socket' === self.exeInfo.io) { + sockServer = net.createServer(function connected(conn) { - // :TODO: This causes an error to be thrown when e.g. 'cp437' is used due to Node buffer changes - //door.setEncoding(this.exeInfo.encoding); - + sockServer.getConnections(function connCount(err, count) { - door.on('data', function doorData(data) { - self.client.term.write(decode(data, self.client.term.outputEncoding)); - }); + // We expect only one connection from our DOOR/emulator/etc. + if(!err && count <= 1) { + self.client.term.output.pipe(conn); + + conn.on('data', doorData); - door.on('close', function closed() { - self.client.term.output.unpipe(door); - self.client.term.output.resume(); - }); + conn.on('end', function ended() { + self.client.term.output.unpipe(conn); + self.client.term.output.resume(); + }); + } + }); + }); - door.on('exit', function exited(code) { - self.client.log.info( { code : code }, 'Door exited'); + sockServer.listen(0, function listening() { + callback(null); + }); + } else { + callback(null); + } + }, + function launch(callback) { + // Expand arg strings, e.g. {dropFile} -> DOOR32.SYS + var args = _.clone(self.exeInfo.args); // we need a copy so the original is not modified - door.removeAllListeners(); + for(var i = 0; i < args.length; ++i) { + args[i] = self.exeInfo.args[i].format({ + dropFile : self.exeInfo.dropFile, + node : self.exeInfo.node.toString(), + inhSocket : self.exeInfo.inhSocket.toString(), + srvPort : sockServer ? sockServer.address().port.toString() : '-1', + }); + } - self.emit('finished'); - }); + var door = pty.spawn(self.exeInfo.cmd, args, { + cols : self.client.term.termWidth, + rows : self.client.term.termHeight, + // :TODO: cwd + env : self.exeInfo.env, + }); + + if('stdio' === self.exeInfo.io) { + self.client.log.debug('Using stdio for door I/O'); + + self.client.term.output.pipe(door); + + door.on('data', doorData); + + door.on('close', function closed() { + self.client.term.output.unpipe(door); + self.client.term.output.resume(); + }); + } else if('socket' === self.exeInfo.io) { + self.client.log.debug( + { port : sockServer.address().port }, + 'Using temporary socket server for door I/O'); + } + + door.on('exit', function exited(code) { + self.client.log.info( { code : code }, 'Door exited'); + + if(sockServer) { + sockServer.close(); + } + + door.removeAllListeners(); + + self.emit('finished'); + }); + } + ], + function complete(err) { + if(err) { + self.client.log.warn( { error : err.toString() }, 'Failed executing door'); + } + } + ); }; \ No newline at end of file diff --git a/mods/abracadabra.js b/mods/abracadabra.js index 583e4376..20fc1b19 100644 --- a/mods/abracadabra.js +++ b/mods/abracadabra.js @@ -12,6 +12,7 @@ var assert = require('assert'); var mkdirp = require('mkdirp'); var paths = require('path'); var _ = require('lodash'); +var net = require('net'); // :TODO: This should really be a system module... needs a little work to allow for such @@ -30,6 +31,25 @@ exports.moduleInfo = { /* Example configuration for LORD under DOSEMU: + { + config: { + name: PimpWars + dropFileType: DORINFO + cmd: qemu-system-i386 + args: [ + "-localtime", + "freedos.img", + "-chardev", + "socket,port={srvPort},nowait,host=localhost,id=s0", + "-device", + "isa-serial,chardev=s0" + ] + io: socket + } + } + + listen: socket | stdio + { "config" : { "name" : "LORD", @@ -129,27 +149,19 @@ function AbracadabraModule(options) { ); }; - this.runDosEmuDoor = function() { - - }; - this.runDoor = function() { var exeInfo = { - cmd : this.config.cmd, - args : this.config.args, + cmd : self.config.cmd, + args : self.config.args, + io : self.config.io || 'stdio', + encoding : self.config.encoding || self.client.term.outputEncoding, + dropFile : self.dropFile.fileName, + node : self.client.node, + inhSocket : self.client.output._handle.fd, }; - - for(var i = 0; i < exeInfo.args.length; ++i) { - exeInfo.args[i] = exeInfo.args[i].format( { - dropFile : self.dropFile.fileName, - node : self.client.node.toString(), - socket : self.client.output._handle.fd.toString(), // ugg! - }); - } - - var doorInstance = new door.Door(this.client, exeInfo); + var doorInstance = new door.Door(self.client, exeInfo); doorInstance.on('finished', function doorFinished() { self.prevMenu(); @@ -163,10 +175,11 @@ function AbracadabraModule(options) { require('util').inherits(AbracadabraModule, MenuModule); +/* AbracadabraModule.prototype.enter = function(client) { AbracadabraModule.super_.prototype.enter.call(this, client); - }; +*/ AbracadabraModule.prototype.leave = function() { AbracadabraModule.super_.prototype.leave.call(this); @@ -177,6 +190,5 @@ AbracadabraModule.prototype.leave = function() { }; AbracadabraModule.prototype.finishedLoading = function() { - this.runDoor(); }; \ No newline at end of file