Improved Windows Terminal

This commit is contained in:
Ylian Saint-Hilaire 2018-12-12 15:34:42 -08:00
parent c65f417e9b
commit d31b3336bd
17 changed files with 2733 additions and 302 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1892
agents/meshcore-01.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
process.on('uncaughtException', function (ex) {
require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": "uncaughtException1: " + ex });
});
@ -652,18 +651,24 @@ function createMeshCore(agent) {
}
// Remote terminal using native pipes
if (process.platform == "win32") {
this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe");
} else {
if (process.platform == "win32")
{
this.httprequest._term = require('win-terminal').Start(80, 25);
this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
this.prependListener('end', function () { this.httprequest._term.end(function () { console.log('Terminal was closed');}); });
//this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe");
} else
{
this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
}
this.httprequest.process.tunnel = this;
this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
this.prependListener('end', function () { this.httprequest.process.kill(); });
}
this.removeAllListeners('data');
this.on('data', onTunnelControlData);
//this.write('MeshCore Terminal Hello');
@ -914,8 +919,15 @@ function createMeshCore(agent) {
} else if (obj.type == 'webrtc0') { // Browser indicates we can start WebRTC switch-over.
if (ws.httprequest.protocol == 1) { // Terminal
// This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket
if (process.platform == 'win32')
{
ws.httprequest._term.unpipe(ws);
}
else
{
ws.httprequest.process.stdout.unpipe(ws);
ws.httprequest.process.stderr.unpipe(ws);
}
} else if (ws.httprequest.protocol == 2) { // Desktop
// This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket
ws.httprequest.desktop.kvm.unpipe(ws);
@ -929,8 +941,16 @@ function createMeshCore(agent) {
} else if (obj.type == 'webrtc1') {
if (ws.httprequest.protocol == 1) { // Terminal
// Switch the user input from websocket to webrtc at this point.
if (process.platform == 'win32')
{
ws.unpipe(ws.httprequest._term);
ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
}
else
{
ws.unpipe(ws.httprequest.process.stdin);
ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
}
ws.resume(); // Resume the websocket to keep receiving control data
} else if (ws.httprequest.protocol == 2) { // Desktop
// Switch the user input from websocket to webrtc at this point.
@ -942,8 +962,15 @@ function createMeshCore(agent) {
} else if (obj.type == 'webrtc2') {
// Other side received websocket end of data marker, start sending data on WebRTC channel
if (ws.httprequest.protocol == 1) { // Terminal
if (process.platform == 'win32')
{
ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
}
else
{
ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
}
} else if (ws.httprequest.protocol == 2) { // Desktop
ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
}

View File

@ -1,3 +1,19 @@
/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var red = 0xFF;
var yellow = 0xFFFF;
var GXxor = 0x6; // src XOR dst

View File

@ -1,3 +1,18 @@
/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var promise = require('promise');
var PPosition = 4;

View File

@ -1,207 +0,0 @@
/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var refTable = {};
function event_switcher_helper(desired_callee, target)
{
this._ObjectID = 'event_switcher';
this.func = function func()
{
var args = [];
for(var i in arguments)
{
args.push(arguments[i]);
}
return (func.target.apply(func.desired, args));
};
this.func.desired = desired_callee;
this.func.target = target;
this.func.self = this;
}
function event_switcher(desired_callee, target)
{
return (new event_switcher_helper(desired_callee, target));
}
function Promise(promiseFunc)
{
this._ObjectID = 'promise';
this.promise = this;
this._internal = { _ObjectID: 'promise.internal', promise: this, func: promiseFunc, completed: false, errors: false, completedArgs: [] };
require('events').EventEmitter.call(this._internal);
this._internal.on('_eventHook', function (eventName, eventCallback)
{
//console.log('hook', eventName, 'errors/' + this.errors + ' completed/' + this.completed);
var r = null;
if (eventName == 'resolved' && !this.errors && this.completed)
{
r = eventCallback.apply(this, this.completedArgs);
if(r!=null)
{
this.emit_returnValue('resolved', r);
}
}
if (eventName == 'rejected' && this.errors && this.completed)
{
eventCallback.apply(this, this.completedArgs);
}
if (eventName == 'settled' && this.completed)
{
eventCallback.apply(this, []);
}
});
this._internal.resolver = function _resolver()
{
_resolver._self.errors = false;
_resolver._self.completed = true;
_resolver._self.completedArgs = [];
var args = ['resolved'];
if (this.emit_returnValue && this.emit_returnValue('resolved') != null)
{
_resolver._self.completedArgs.push(this.emit_returnValue('resolved'));
args.push(this.emit_returnValue('resolved'));
}
else
{
for (var a in arguments)
{
_resolver._self.completedArgs.push(arguments[a]);
args.push(arguments[a]);
}
}
_resolver._self.emit.apply(_resolver._self, args);
_resolver._self.emit('settled');
};
this._internal.rejector = function _rejector()
{
_rejector._self.errors = true;
_rejector._self.completed = true;
_rejector._self.completedArgs = [];
var args = ['rejected'];
for (var a in arguments)
{
_rejector._self.completedArgs.push(arguments[a]);
args.push(arguments[a]);
}
_rejector._self.emit.apply(_rejector._self, args);
_rejector._self.emit('settled');
};
this.catch = function(func)
{
this._internal.once('rejected', event_switcher(this, func).func);
}
this.finally = function (func)
{
this._internal.once('settled', event_switcher(this, func).func);
};
this.then = function (resolved, rejected)
{
if (resolved) { this._internal.once('resolved', event_switcher(this, resolved).func); }
if (rejected) { this._internal.once('rejected', event_switcher(this, rejected).func); }
var retVal = new Promise(function (r, j) { });
this._internal.once('resolved', retVal._internal.resolver);
this._internal.once('rejected', retVal._internal.rejector);
retVal.parentPromise = this;
return (retVal);
};
this._internal.resolver._self = this._internal;
this._internal.rejector._self = this._internal;;
try
{
promiseFunc.call(this, this._internal.resolver, this._internal.rejector);
}
catch(e)
{
this._internal.errors = true;
this._internal.completed = true;
this._internal.completedArgs = [e];
this._internal.emit('rejected', e);
this._internal.emit('settled');
}
if(!this._internal.completed)
{
// Save reference of this object
refTable[this._internal._hashCode()] = this._internal;
this._internal.once('settled', function () { refTable[this._hashCode()] = null; });
}
}
Promise.resolve = function resolve()
{
var retVal = new Promise(function (r, j) { });
var args = [];
for (var i in arguments)
{
args.push(arguments[i]);
}
retVal._internal.resolver.apply(retVal._internal, args);
return (retVal);
};
Promise.reject = function reject() {
var retVal = new Promise(function (r, j) { });
var args = [];
for (var i in arguments) {
args.push(arguments[i]);
}
retVal._internal.rejector.apply(retVal._internal, args);
return (retVal);
};
Promise.all = function all(promiseList)
{
var ret = new Promise(function (res, rej)
{
this.__rejector = rej;
this.__resolver = res;
this.__promiseList = promiseList;
this.__done = false;
this.__count = 0;
});
for (var i in promiseList)
{
promiseList[i].then(function ()
{
// Success
if(++ret.__count == ret.__promiseList.length)
{
ret.__done = true;
ret.__resolver(ret.__promiseList);
}
}, function (arg)
{
// Failure
if(!ret.__done)
{
ret.__done = true;
ret.__rejector(arg);
}
});
}
if (promiseList.length == 0)
{
ret.__resolver(promiseList);
}
return (ret);
};
module.exports = Promise;

View File

@ -214,6 +214,13 @@ function serviceManager()
throw ('could not find service: ' + name);
}
}
else
{
this.isAdmin = function isAdmin()
{
return (require('user-sessions').isRoot());
}
}
this.installService = function installService(options)
{
if (process.platform == 'win32')
@ -273,6 +280,8 @@ function serviceManager()
}
if(process.platform == 'linux')
{
if (!this.isAdmin()) { throw ('Installing as Service, requires root'); }
switch (this.getServiceType())
{
case 'init':
@ -311,14 +320,70 @@ function serviceManager()
break;
}
}
if(process.platform == 'darwin')
{
if (!this.isAdmin()) { throw ('Installing as Service, requires root'); }
// Mac OS
var stdoutpath = (options.stdout ? ('<key>StandardOutPath</key>\n<string>' + options.stdout + '</string>') : '');
var autoStart = (options.startType == 'AUTO_START' ? '<true/>' : '<false/>');
var params = ' <key>ProgramArguments</key>\n';
params += ' <array>\n';
params += (' <string>/usr/local/mesh_services/' + options.name + '/' + options.name + '</string>\n');
if(options.parameters)
{
for(var itm in options.parameters)
{
params += (' <string>' + options.parameters[itm] + '</string>\n');
}
}
params += ' </array>\n';
var plist = '<?xml version="1.0" encoding="UTF-8"?>\n';
plist += '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
plist += '<plist version="1.0">\n';
plist += ' <dict>\n';
plist += ' <key>Label</key>\n';
plist += (' <string>' + options.name + '</string>\n');
plist += (params + '\n');
plist += ' <key>WorkingDirectory</key>\n';
plist += (' <string>/usr/local/mesh_services/' + options.name + '</string>\n');
plist += (stdoutpath + '\n');
plist += ' <key>RunAtLoad</key>\n';
plist += (autoStart + '\n');
plist += ' </dict>\n';
plist += '</plist>';
if (!require('fs').existsSync('/usr/local/mesh_services')) { require('fs').mkdirSync('/usr/local/mesh_services'); }
if (!require('fs').existsSync('/Library/LaunchDaemons/' + options.name + '.plist'))
{
if (!require('fs').existsSync('/usr/local/mesh_services/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_services/' + options.name); }
if (options.binary)
{
require('fs').writeFileSync('/usr/local/mesh_services/' + options.name + '/' + options.name, options.binary);
}
else
{
require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.name);
}
require('fs').writeFileSync('/Library/LaunchDaemons/' + options.name + '.plist', plist);
var m = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.name).mode;
m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP);
require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.name, m);
}
else
{
throw ('Service: ' + options.name + ' already exists');
}
}
}
this.uninstallService = function uninstallService(name)
{
if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); }
if (typeof (name) == 'object') { name = name.name; }
if (process.platform == 'win32')
{
if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); }
var service = this.getService(name);
if (service.status.state == undefined || service.status.state == 'STOPPED')
{
@ -388,6 +453,39 @@ function serviceManager()
break;
}
}
else if(process.platform == 'darwin')
{
if (require('fs').existsSync('/Library/LaunchDaemons/' + name + '.plist'))
{
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.on('data', function (chunk) { });
child.stdin.write('launchctl stop ' + name + '\n');
child.stdin.write('launchctl unload /Library/LaunchDaemons/' + name + '.plist\n');
child.stdin.write('exit\n');
child.waitExit();
try
{
require('fs').unlinkSync('/usr/local/mesh_services/' + name + '/' + name);
require('fs').unlinkSync('/Library/LaunchDaemons/' + name + '.plist');
}
catch(e)
{
throw ('Error uninstalling service: ' + name + ' => ' + e);
}
try
{
require('fs').rmdirSync('/usr/local/mesh_services/' + name);
}
catch(e)
{}
}
else
{
throw ('Service: ' + name + ' does not exist');
}
}
}
if(process.platform == 'linux')
{

View File

@ -1,3 +1,18 @@
/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
function _Scan()
{

View File

@ -1,3 +1,19 @@
/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var MemoryStream = require('MemoryStream');
var WindowsChildScript = 'var parent = require("ScriptContainer");var Wireless = require("wifi-scanner-windows");Wireless.on("Scan", function (ap) { parent.send(ap); });Wireless.Scan();';

View File

@ -17,6 +17,8 @@ limitations under the License.
var WH_CALLWNDPROC = 4;
var WM_QUIT = 0x0012;
var GM = require('_GenericMarshal');
function WindowsMessagePump(options)
{
this._ObjectID = 'win-message-pump';
@ -27,92 +29,94 @@ function WindowsMessagePump(options)
emitterUtils.createEvent('message');
emitterUtils.createEvent('exit');
this._child = require('ScriptContainer').Create({ processIsolation: 0 });
this._child.MessagePump = this;
this._child.prependListener('~', function _childFinalizer() { this.MessagePump.emit('exit', 0); this.MessagePump.stop(); });
this._child.once('exit', function onExit(code) { this.MessagePump.emit('exit', code); });
this._child.once('ready', function onReady()
{
var execString =
"var m = require('_GenericMarshal');\
var h = null;\
var k = m.CreateNativeProxy('Kernel32.dll');\
k.CreateMethod('GetLastError');\
k.CreateMethod('GetModuleHandleA');\
var u = m.CreateNativeProxy('User32.dll');\
u.CreateMethod('GetMessageA');\
u.CreateMethod('CreateWindowExA');\
u.CreateMethod('TranslateMessage');\
u.CreateMethod('DispatchMessageA');\
u.CreateMethod('RegisterClassExA');\
u.CreateMethod('DefWindowProcA');\
var wndclass = m.CreateVariable(m.PointerSize == 4 ? 48 : 80);\
wndclass.hinstance = k.GetModuleHandleA(0);\
wndclass.cname = m.CreateVariable('MainWWWClass');\
wndclass.wndproc = m.GetGenericGlobalCallback(4);\
wndclass.toBuffer().writeUInt32LE(wndclass._size);\
wndclass.cname.pointerBuffer().copy(wndclass.Deref(m.PointerSize == 4 ? 40 : 64, m.PointerSize).toBuffer());\
wndclass.wndproc.pointerBuffer().copy(wndclass.Deref(8, m.PointerSize).toBuffer());\
wndclass.hinstance.pointerBuffer().copy(wndclass.Deref(m.PointerSize == 4 ? 20 : 24, m.PointerSize).toBuffer());\
wndclass.wndproc.on('GlobalCallback', function onWndProc(xhwnd, xmsg, wparam, lparam)\
{\
if(h==null || h.Val == xhwnd.Val)\
{\
require('ScriptContainer').send({message: xmsg.Val, wparam: wparam.Val, lparam: lparam.Val, lparam_hex: lparam.pointerBuffer().toString('hex')});\
var retVal = u.DefWindowProcA(xhwnd, xmsg, wparam, lparam);\
return(retVal);\
}\
});\
u.RegisterClassExA(wndclass);\
h = u.CreateWindowExA(0x00000088, wndclass.cname, 0, 0x00800000, 0, 0, 100, 100, 0, 0, 0, 0);\
if(h.Val == 0)\
{\
require('ScriptContainer').send({error: 'Error Creating Hidden Window'});\
process.exit();\
}\
require('ScriptContainer').send({hwnd: h.pointerBuffer().toString('hex')});\
require('ScriptContainer').on('data', function onData(jmsg)\
{\
if(jmsg.listen)\
{\
var msg = m.CreateVariable(m.PointerSize == 4 ? 28 : 48);\
while(u.GetMessageA(msg, h, 0, 0).Val>0)\
{\
u.TranslateMessage(msg);\
u.DispatchMessageA(msg);\
}\
process.exit();\
}\
});";
this._msg = GM.CreateVariable(GM.PointerSize == 4 ? 28 : 48);
this._kernel32 = GM.CreateNativeProxy('Kernel32.dll');
this._kernel32.mp = this;
this._kernel32.CreateMethod('GetLastError');
this._kernel32.CreateMethod('GetModuleHandleA');
this.ExecuteString(execString);
});
this._child.on('data', function onChildData(msg)
this._user32 = GM.CreateNativeProxy('User32.dll');
this._user32.mp = this;
this._user32.CreateMethod('GetMessageA');
this._user32.CreateMethod('CreateWindowExA');
this._user32.CreateMethod('TranslateMessage');
this._user32.CreateMethod('DispatchMessageA');
this._user32.CreateMethod('RegisterClassExA');
this._user32.CreateMethod('DefWindowProcA');
this._user32.CreateMethod('PostMessageA');
this.wndclass = GM.CreateVariable(GM.PointerSize == 4 ? 48 : 80);
this.wndclass.mp = this;
this.wndclass.hinstance = this._kernel32.GetModuleHandleA(0);
this.wndclass.cname = GM.CreateVariable('MainWWWClass');
this.wndclass.wndproc = GM.GetGenericGlobalCallback(4);
this.wndclass.wndproc.mp = this;
this.wndclass.toBuffer().writeUInt32LE(this.wndclass._size);
this.wndclass.cname.pointerBuffer().copy(this.wndclass.Deref(GM.PointerSize == 4 ? 40 : 64, GM.PointerSize).toBuffer());
this.wndclass.wndproc.pointerBuffer().copy(this.wndclass.Deref(8, GM.PointerSize).toBuffer());
this.wndclass.hinstance.pointerBuffer().copy(this.wndclass.Deref(GM.PointerSize == 4 ? 20 : 24, GM.PointerSize).toBuffer());
this.wndclass.wndproc.on('GlobalCallback', function onWndProc(xhwnd, xmsg, wparam, lparam)
{
if (msg.hwnd)
if (this.mp._hwnd != null && this.mp._hwnd.Val == xhwnd.Val)
{
var m = require('_GenericMarshal');
this._hwnd = m.CreatePointer(Buffer.from(msg.hwnd, 'hex'));
this.MessagePump.emit('hwnd', this._hwnd);
this.send({ listen: this.MessagePump._options.filter });
// This is for us
this.mp.emit('message', { message: xmsg.Val, wparam: wparam.Val, lparam: lparam.Val, lparam_hex: lparam.pointerBuffer().toString('hex') });
return (this.mp._user32.DefWindowProcA(xhwnd, xmsg, wparam, lparam));
}
else if(msg.message)
else if(this.mp._hwnd == null && this.CallingThread() == this.mp._user32.RegisterClassExA.async.threadId())
{
this.MessagePump.emit('message', msg);
// This message was generated from our CreateWindowExA method
return (this.mp._user32.DefWindowProcA(xhwnd, xmsg, wparam, lparam));
}
});
this._user32.RegisterClassExA.async(this.wndclass).then(function ()
{
this.nativeProxy.CreateWindowExA.async(this.nativeProxy.RegisterClassExA.async, 0x00000088, this.nativeProxy.mp.wndclass.cname, 0, 0x00800000, 0, 0, 100, 100, 0, 0, 0, 0)
.then(function(h)
{
if (h.Val == 0)
{
// Error creating hidden window
this.nativeProxy.mp.emit('error', 'Error creating hidden window');
}
else
{
console.log('Received: ', msg);
this.nativeProxy.mp._hwnd = h;
this.nativeProxy.mp.emit('hwnd', h);
this.nativeProxy.mp._startPump();
}
});
});
this._startPump = function _startPump()
{
this._user32.GetMessageA.async(this._user32.RegisterClassExA.async, this._msg, this._hwnd, 0, 0).then(function (r)
{
if(r.Val > 0)
{
this.nativeProxy.TranslateMessage.async(this.nativeProxy.RegisterClassExA.async, this.nativeProxy.mp._msg).then(function ()
{
this.nativeProxy.DispatchMessageA.async(this.nativeProxy.RegisterClassExA.async, this.nativeProxy.mp._msg).then(function ()
{
this.nativeProxy.mp._startPump();
});
});
}
else
{
// We got a 'QUIT' message
delete this.nativeProxy.mp._hwnd;
this.nativeProxy.mp.emit('exit', 0);
}
}, function (err) { this.nativeProxy.mp.stop(); });
}
this.stop = function stop()
{
if(this._child && this._child._hwnd)
if (this._hwnd)
{
var marshal = require('_GenericMarshal');
var User32 = marshal.CreateNativeProxy('User32.dll');
User32.CreateMethod('PostMessageA');
User32.PostMessageA(this._child._hwnd, WM_QUIT, 0, 0);
this._user32.PostMessageA(this._hwnd, WM_QUIT, 0, 0);
}
};
}

View File

@ -0,0 +1,555 @@
/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var promise = require('promise');
var duplex = require('stream').Duplex;
var SW_HIDE = 0;
var SW_MINIMIZE = 6;
var STARTF_USESHOWWINDOW = 0x1;
var STD_INPUT_HANDLE = -10;
var STD_OUTPUT_HANDLE = -11;
var EVENT_CONSOLE_CARET = 0x4001;
var EVENT_CONSOLE_END_APPLICATION = 0x4007;
var WINEVENT_OUTOFCONTEXT = 0x000;
var WINEVENT_SKIPOWNPROCESS = 0x0002;
var CREATE_NEW_PROCESS_GROUP = 0x200;
var EVENT_CONSOLE_UPDATE_REGION = 0x4002;
var EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003;
var EVENT_CONSOLE_UPDATE_SCROLL = 0x4004;
var EVENT_CONSOLE_LAYOUT = 0x4005;
var EVENT_CONSOLE_START_APPLICATION = 0x4006;
var KEY_EVENT = 0x1;
var MAPVK_VK_TO_VSC = 0;
var WM_QUIT = 0x12;
var GM = require('_GenericMarshal');
var si = GM.CreateVariable(GM.PointerSize == 4 ? 68 : 104);
var pi = GM.CreateVariable(GM.PointerSize == 4 ? 16 : 24);
si.Deref(0, 4).toBuffer().writeUInt32LE(GM.PointerSize == 4 ? 68 : 104); // si.cb
si.Deref(GM.PointerSize == 4 ? 48 : 64, 2).toBuffer().writeUInt16LE(SW_HIDE | SW_MINIMIZE); // si.wShowWindow
si.Deref(GM.PointerSize == 4 ? 44 : 60, 4).toBuffer().writeUInt32LE(STARTF_USESHOWWINDOW); // si.dwFlags;
var MSG = GM.CreateVariable(GM.PointerSize == 4 ? 28 : 48);
function windows_terminal()
{
this._ObjectID = 'windows_terminal';
this._user32 = GM.CreateNativeProxy('User32.dll');
this._user32.CreateMethod('DispatchMessageA');
this._user32.CreateMethod('GetMessageA');
this._user32.CreateMethod('MapVirtualKeyA');
this._user32.CreateMethod('PostThreadMessageA');
this._user32.CreateMethod('SetWinEventHook');
this._user32.CreateMethod('ShowWindow');
this._user32.CreateMethod('TranslateMessage');
this._user32.CreateMethod('UnhookWinEvent');
this._user32.CreateMethod('VkKeyScanA');
this._user32.terminal = this;
this._kernel32 = GM.CreateNativeProxy('Kernel32.dll');
this._kernel32.CreateMethod('AllocConsole');
this._kernel32.CreateMethod('CreateProcessA');
this._kernel32.CreateMethod('CloseHandle');
this._kernel32.CreateMethod('FillConsoleOutputAttribute');
this._kernel32.CreateMethod('FillConsoleOutputCharacterA');
this._kernel32.CreateMethod('GetConsoleScreenBufferInfo');
this._kernel32.CreateMethod('GetConsoleWindow');
this._kernel32.CreateMethod('GetLastError');
this._kernel32.CreateMethod('GetStdHandle');
this._kernel32.CreateMethod('GetThreadId');
this._kernel32.CreateMethod('ReadConsoleOutputA');
this._kernel32.CreateMethod('SetConsoleCursorPosition');
this._kernel32.CreateMethod('SetConsoleScreenBufferSize');
this._kernel32.CreateMethod('SetConsoleWindowInfo');
this._kernel32.CreateMethod('TerminateProcess');
this._kernel32.CreateMethod('WaitForSingleObject');
this._kernel32.CreateMethod('WriteConsoleInputA');
var currentX = 0;
var currentY = 0;
this._scrx = 0;
this._scry = 0;
this.SendCursorUpdate = function()
{
var newCsbi = GM.CreateVariable(22);
if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, newCsbi).Val == 0) { return; }
if (newCsbi.Deref(4,2).toBuffer().readUInt16LE() != this.currentX || newCsbi.Deref(6,2).toBuffer().readUInt16LE() != this.currentY)
{
//wchar_t mywbuf[512];
//swprintf(mywbuf, 512, TEXT("csbi.dwCursorPosition.X = %d, csbi.dwCursorPosition.Y = %d, newCsbi.dwCursorPosition.X = %d, newCsbi.dwCursorPosition.Y = %d\r\n"), csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, newCsbi.dwCursorPosition.X, newCsbi.dwCursorPosition.Y);
//OutputDebugString(mywbuf);
//m_viewOffset = newCsbi.srWindow.Top;
//WriteMoveCursor((SerialAgent *)this->sa, (char)(newCsbi.dwCursorPosition.Y - m_viewOffset), (char)(newCsbi.dwCursorPosition.X - m_viewOffset));
//LowStackSendData((SerialAgent *)(this->sa), "", 0);
this.currentX = newCsbi.Deref(4,2).toBuffer().readUInt16LE();
this.currentY = newCsbi.Deref(6,2).toBuffer().readUInt16LE();
}
}
this.ClearScreen = function()
{
var CONSOLE_SCREEN_BUFFER_INFO = GM.CreateVariable(22);
if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; }
var coordScreen = GM.CreateVariable(4);
var dwConSize = CONSOLE_SCREEN_BUFFER_INFO.Deref(0,2).toBuffer().readUInt16LE(0) * CONSOLE_SCREEN_BUFFER_INFO.Deref(2,2).toBuffer().readUInt16LE(0);
var cCharsWritten = GM.CreateVariable(4);
// Fill the entire screen with blanks.
if (this._kernel32.FillConsoleOutputCharacterA(this._stdoutput, 32, dwConSize, coordScreen.Deref(0,4).toBuffer().readUInt32LE(), cCharsWritten).Val == 0) { return; }
// Get the current text attribute.
if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO).Val == 0) { return; }
// Set the buffer's attributes accordingly.
if (this._kernel32.FillConsoleOutputAttribute(this._stdoutput, CONSOLE_SCREEN_BUFFER_INFO.Deref(8, 2).toBuffer().readUInt16LE(0), dwConSize, coordScreen.Deref(0, 4).toBuffer().readUInt32LE(), cCharsWritten).Val == 0) { return; }
// Put the cursor at its home coordinates.
this._kernel32.SetConsoleCursorPosition(this._stdoutput, coordScreen.Deref(0, 4).toBuffer().readUInt32LE());
// Put the window to top-left.
var rect = GM.CreateVariable(8);
var srWindow = CONSOLE_SCREEN_BUFFER_INFO.Deref(10,8).toBuffer();
rect.Deref(4,2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(4) - srWindow.readUInt16LE(0));
rect.Deref(6,2).toBuffer().writeUInt16LE(srWindow.readUInt16LE(6) - srWindow.readUInt16LE(2));
this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect);
}
this.Start = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
{
if(this._kernel32.GetConsoleWindow().Val == 0)
{
if(this._kernel32.AllocConsole().Val == 0)
{
throw ('AllocConsole failed with: ' + this._kernel32.GetLastError().Val);
}
}
this._stdinput = this._kernel32.GetStdHandle(STD_INPUT_HANDLE);
this._stdoutput = this._kernel32.GetStdHandle(STD_OUTPUT_HANDLE);
this._connected = false;
var coordScreen = GM.CreateVariable(4);
coordScreen.Deref(0, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH);
coordScreen.Deref(2, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT);
var rect = GM.CreateVariable(8);
rect.Deref(4, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_WIDTH - 1);
rect.Deref(6, 2).toBuffer().writeUInt16LE(CONSOLE_SCREEN_HEIGHT - 1);
if(this._kernel32.SetConsoleWindowInfo(this._stdoutput, 1, rect).Val == 0)
{
throw ('Failed to set Console Screen Size');
}
if(this._kernel32.SetConsoleScreenBufferSize(this._stdoutput, coordScreen.Deref(0,4).toBuffer().readUInt32LE()).Val == 0)
{
throw ('Failed to set Console Buffer Size');
}
this.ClearScreen();
this._hookThread().then(function ()
{
// Hook Ready
this.terminal.StartCommand();
}, console.log);
this._stream = new duplex({
'write': function (chunk, flush)
{
if (!this.terminal.connected)
{
//console.log('_write: ' + chunk);
if (!this._promise.chunk)
{
this._promise.chunk = [];
}
if (typeof (chunk) == 'string')
{
this._promise.chunk.push(chunk);
}
else
{
this._promise.chunk.push(Buffer.alloc(chunk.length));
chunk.copy(this._promise.chunk.peek());
}
this._promise.chunk.peek().flush = flush;
this._promise.then(function ()
{
var buf;
while(this.chunk.length > 0)
{
buf = this.chunk.shift();
this.terminal._WriteBuffer(buf);
buf.flush();
}
});
}
else
{
//console.log('writeNOW: ' + chunk);
this.terminal._WriteBuffer(chunk);
flush();
}
},
'final': function (flush)
{
var p = this.terminal._stop();
p.__flush = flush;
p.then(function () { this.__flush(); });
}
});
this._stream.terminal = this;
this._stream._promise = new promise(function (res, rej) { this._res = res; this._rej = rej; });
this._stream._promise.terminal = this;
return (this._stream);
};
this._stop = function()
{
if (this.stopping) { return (this.stopping); }
console.log('Stopping Terminal...');
this.stopping = new promise(function (res, rej) { this._res = res; this._rej = rej; });
var threadID = this._kernel32.GetThreadId(this._user32.SetWinEventHook.async.thread()).Val;
this._user32.PostThreadMessageA(threadID, WM_QUIT, 0, 0);
return (this.stopping);
}
this._hookThread = function ()
{
var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; });
ret.terminal = this;
this._ConsoleWinEventProc = GM.GetGenericGlobalCallback(7);
this._ConsoleWinEventProc.terminal = this;
var p = this._user32.SetWinEventHook.async(EVENT_CONSOLE_CARET, EVENT_CONSOLE_END_APPLICATION, 0, this._ConsoleWinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
p.ready = ret;
p.terminal = this;
p.then(function (hwinEventHook)
{
if (hwinEventHook.Val == 0)
{
this.ready._rej('Error calling SetWinEventHook');
}
else
{
this.terminal.hwinEventHook = hwinEventHook;
this.ready._res();
this.terminal._GetMessage();
}
});
this._ConsoleWinEventProc.on('GlobalCallback', function (hhook, dwEvent, hwnd, idObject, idChild, idEventThread, swmsEventTime)
{
if (!this.terminal.hwinEventHook || this.terminal.hwinEventHook.Val != hhook.Val) { return; }
var buffer = null;
switch (dwEvent.Val)
{
case EVENT_CONSOLE_CARET:
break;
case EVENT_CONSOLE_UPDATE_REGION:
if (!this.terminal.connected)
{
this.terminal.connected = true; this.terminal._stream._promise._res();
}
if (this.terminal._scrollTimer == null)
{
buffer = this.terminal._GetScreenBuffer(LOWORD(idObject.Val), HIWORD(idObject.Val), LOWORD(idChild.Val), HIWORD(idChild.Val));
//console.log('UPDATE REGION: [Left: ' + LOWORD(idObject.Val) + ' Top: ' + HIWORD(idObject.Val) + ' Right: ' + LOWORD(idChild.Val) + ' Bottom: ' + HIWORD(idChild.Val) + ']');
this.terminal._SendDataBuffer(buffer);
}
break;
case EVENT_CONSOLE_UPDATE_SIMPLE:
//console.log('UPDATE SIMPLE: [X: ' + LOWORD(idObject.Val) + ' Y: ' + HIWORD(idObject.Val) + ' Char: ' + LOWORD(idChild.Val) + ' Attr: ' + HIWORD(idChild.Val) + ']');
var simplebuffer = { data: [Buffer.alloc(1, LOWORD(idChild.Val))], attributes: [HIWORD(idChild.Val)], width: 1, height: 1, x: LOWORD(idObject.Val)+1, y: HIWORD(idObject.Val) };
this.terminal._SendDataBuffer(simplebuffer);
break;
case EVENT_CONSOLE_UPDATE_SCROLL:
//console.log('UPDATE SCROLL: [dx: ' + idObject.Val + ' dy: ' + idChild.Val + ']');
this.terminal._SendScroll(idObject.Val, idChild.Val);
break;
case EVENT_CONSOLE_LAYOUT:
//console.log('CONSOLE_LAYOUT');
//snprintf( Buf, 512, "Event Console LAYOUT!\r\n");
//SendLayout();
break;
case EVENT_CONSOLE_START_APPLICATION:
//console.log('START APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']');
//snprintf( Buf, 512, "Event Console START APPLICATION!\r\nProcess ID: %d - Child ID: %d\r\n\r\n", (int)idObject, (int)idChild);
//SendConsoleEvent(dwEvent, idObject, idChild);
break;
case EVENT_CONSOLE_END_APPLICATION:
if(idObject.Val == this.terminal._hProcessID)
{
//console.log('END APPLICATION: [PID: ' + idObject.Val + ' CID: ' + idChild.Val + ']');
this.terminal._stop().then(function () { console.log('STOPPED'); });
}
break;
default:
//snprintf(Buf, 512, "unknown console event.\r\n");
console.log('Unknown event: ' + dwEvent.Val);
break;
}
//mbstowcs_s(&l, wBuf, Buf, 512);
//OutputDebugString(wBuf);
});
return (ret);
}
this._GetMessage = function()
{
if (this._user32.abort) { console.log('aborting loop'); return; }
this._user32.GetMessageA.async(this._user32.SetWinEventHook.async, MSG, 0, 0, 0).then(function (ret)
{
//console.log('GetMessage Response');
if(ret.Val != 0)
{
if (ret.Val == -1)
{
// handle the error and possibly exit
}
else
{
//console.log('TranslateMessage');
this.nativeProxy._user32.TranslateMessage.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function ()
{
//console.log('DispatchMessage');
this.nativeProxy._user32.DispatchMessageA.async(this.nativeProxy.user32.SetWinEventHook.async, MSG).then(function ()
{
this.nativeProxy.terminal._GetMessage();
}, console.log);
}, console.log);
}
}
else
{
this.nativeProxy.UnhookWinEvent.async(this.nativeProxy.terminal._user32.SetWinEventHook.async, this.nativeProxy.terminal.hwinEventHook)
.then(function ()
{
this.nativeProxy.terminal.stopping._res();
if(this.nativeProxy.terminal._kernel32.TerminateProcess(this.nativeProxy.terminal._hProcess, 1067).Val == 0)
{
var e = this.nativeProxy.terminal._kernel32.GetLastError().Val;
console.log('Unable to kill Terminal Process, error: ' + e);
}
this.nativeProxy.terminal.stopping = null;
}, function (err)
{
console.log('REJECTED_UnhookWinEvent: ' + err);
});
}
}, function (err)
{
// Get Message Failed
console.log('REJECTED_GETMessage: ' + err);
});
}
this._WriteBuffer = function(buf)
{
for (var i = 0; i < buf.length; ++i)
{
if (typeof (buf) == 'string')
{
this._WriteCharacter(buf.charCodeAt(i), false);
}
else
{
this._WriteCharacter(buf[i], false);
}
}
}
this._WriteCharacter = function(key, bControlKey)
{
var rec = GM.CreateVariable(20);
rec.Deref(0,2).toBuffer().writeUInt16LE(KEY_EVENT); // rec.EventType
rec.Deref(4,4).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.bKeyDown
rec.Deref(16, 4).toBuffer().writeUInt32LE(bControlKey); // rec.Event.KeyEvent.dwControlKeyState
rec.Deref(14, 1).toBuffer()[0] = key; // rec.Event.KeyEvent.uChar.AsciiChar
rec.Deref(8, 2).toBuffer().writeUInt16LE(1); // rec.Event.KeyEvent.wRepeatCount
rec.Deref(10, 2).toBuffer().writeUInt16LE(this._user32.VkKeyScanA(key).Val); // rec.Event.KeyEvent.wVirtualKeyCode
rec.Deref(12, 2).toBuffer().writeUInt16LE(this._user32.MapVirtualKeyA(this._user32.VkKeyScanA(key).Val, MAPVK_VK_TO_VSC).Val);
var dwWritten = GM.CreateVariable(4);
if(this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val == 0) { return(false); }
rec.Deref(4,4).toBuffer().writeUInt16LE(0); // rec.Event.KeyEvent.bKeyDown
return(this._kernel32.WriteConsoleInputA(this._stdinput, rec, 1, dwWritten).Val != 0);
}
this._GetScreenBuffer = function(sx, sy, ex, ey)
{
// get the current visible screen buffer
var info = GM.CreateVariable(22);
if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw('Error getting screen buffer info'); }
var nWidth = info.Deref(14,2).toBuffer().readUInt16LE() - info.Deref(10,2).toBuffer().readUInt16LE() + 1;
var nHeight = info.Deref(16,2).toBuffer().readUInt16LE() - info.Deref(12,2).toBuffer().readUInt16LE() + 1;
if (arguments[3] == null)
{
// Use Default Parameters
sx = 0;
sy = 0;
ex = nWidth-1;
ey = nHeight-1;
}
else
{
if(this._scrx != 0)
{
sx += this._scrx;
ex += this._scrx;
}
if(this._scry != 0)
{
sy += this._scry;
ey += this._scry;
}
this._scrx = this._scry = 0;
}
var nBuffer = GM.CreateVariable((ex-sx+1) * (ey-sy+1) * 4);
var size = GM.CreateVariable(4);
size.Deref(0,2).toBuffer().writeUInt16LE(ex-sx+1, 0);
size.Deref(2, 2).toBuffer().writeUInt16LE(ey-sy+1, 0);
var startCoord = GM.CreateVariable(4);
startCoord.Deref(0, 2).toBuffer().writeUInt16LE(0, 0);
startCoord.Deref(2, 2).toBuffer().writeUInt16LE(0, 0);
var region = GM.CreateVariable(8);
region.buffer = region.toBuffer();
region.buffer.writeUInt16LE(sx, 0);
region.buffer.writeUInt16LE(sy, 2);
region.buffer.writeUInt16LE(ex, 4);
region.buffer.writeUInt16LE(ey, 6);
if (this._kernel32.ReadConsoleOutputA(this._stdoutput, nBuffer, size.Deref(0, 4).toBuffer().readUInt32LE(), startCoord.Deref(0, 4).toBuffer().readUInt32LE(), region).Val == 0)
{
throw('Unable to read Console Output');
}
// Lets convert the buffer into something simpler
//var retVal = { data: Buffer.alloc((dw - dx + 1) * (dh - dy + 1)), attributes: Buffer.alloc((dw - dx + 1) * (dh - dy + 1)), width: dw - dx + 1, height: dh - dy + 1, x: dx, y: dy };
var retVal = { data: [], attributes: [], width: ex - sx + 1, height: ey - sy + 1, x: sx, y: sy };
var x, y, line, ifo;
var tmp;
var lineWidth = ex - sx + 1;
for (y = 0; y <= (ey - sy) ; ++y)
{
retVal.data.push(Buffer.alloc(lineWidth));
retVal.attributes.push(Buffer.alloc(lineWidth));
line = nBuffer.Deref(y * lineWidth * 4, lineWidth * 4).toBuffer();
for(x = 0; x < lineWidth; ++x)
{
retVal.data.peek()[x] = line[x * 4];
retVal.attributes.peek()[x] = line[2 + (x * 4)];
}
}
return (retVal);
}
this._SendDataBuffer = function(data)
{
// { data, attributes, width, height, x, y }
var dy, line, attr;
for(dy = 0; dy < data.height; ++dy)
{
line = data.data[dy];
attr = data.attributes[dy];
line.s = line.toString();
//line = data.data.slice(data.width * dy, (data.width * dy) + data.width);
//attr = data.attributes.slice(data.width * dy, (data.width * dy) + data.width);
this._stream.push(TranslateLine(data.x, data.y + dy, line, attr));
}
}
this._SendScroll = function _SendScroll(dx, dy)
{
if (this._scrollTimer)
{
return;
}
var info = GM.CreateVariable(22);
if (this._kernel32.GetConsoleScreenBufferInfo(this._stdoutput, info).Val == 0) { throw ('Error getting screen buffer info'); }
var nWidth = info.Deref(14, 2).toBuffer().readUInt16LE() - info.Deref(10, 2).toBuffer().readUInt16LE() + 1;
var nHeight = info.Deref(16, 2).toBuffer().readUInt16LE() - info.Deref(12, 2).toBuffer().readUInt16LE() + 1;
this._stream.push(GetEsc('H', nHeight-1, 0));
for (var i = 0; i > nHeight; ++i)
{
this._stream.push(Buffer.from('\r\n'));
}
var buffer = this._GetScreenBuffer(0, 0, nWidth - 1, nHeight - 1);
this._SendDataBuffer(buffer);
this._scrollTimer = setTimeout(function (self, nw, nh)
{
var buffer = self._GetScreenBuffer(0, 0, nw - 1, nh - 1);
self._SendDataBuffer(buffer);
self._scrollTimer = null;
}, 250, this, nWidth, nHeight);
}
this.StartCommand = function StartCommand()
{
if(this._kernel32.CreateProcessA(GM.CreateVariable(process.env['windir'] + '\\system32\\cmd.exe'), 0, 0, 0, 1, CREATE_NEW_PROCESS_GROUP, 0, 0, si, pi).Val == 0)
{
console.log('Error Spawning CMD');
return;
}
this._kernel32.CloseHandle(pi.Deref(GM.PointerSize, GM.PointerSize).Deref()); // pi.hThread
this._hProcess = pi.Deref(0, GM.PointerSize).Deref(); // pi.hProcess
this._hProcessID = pi.Deref(GM.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE(); // pi.dwProcessId
//console.log('Ready => hProcess: ' + this._hProcess._ptr + ' PID: ' + this._hProcessID);
}
}
function LOWORD(val)
{
return (val & 0xFFFF);
}
function HIWORD(val)
{
return ((val >> 16) & 0xFFFF);
}
function GetEsc(CodeCharStr, arg1, arg2)
{
return (Buffer.from('\x1B[' + arg1 + ';' + arg2 + CodeCharStr));
//return (Buffer.from('*[' + arg1 + ';' + arg2 + CodeCharStr));
}
function TranslateLine(x, y, data, attributes)
{
return (Buffer.concat([GetEsc('H', y, x), data]));
}
module.exports = new windows_terminal();

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.2.4-g",
"version": "0.2.4-h",
"keywords": [
"Remote Management",
"Intel AMT",