From 1fe977e512e373eb9233e92a0abe0e600011fa0a Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Sat, 20 Jun 2020 16:28:16 -0700 Subject: [PATCH] Started adding support for mid-stream AMTKVM recording. --- amt-desktop-0.0.2.js | 71 +++++++++++++++++++++++++++++++++++++ amt-ider-node-0.0.1.js | 79 ++++++++++++++++++++++++------------------ index.html | 66 ++++++++++++++++++++++++----------- 3 files changed, 163 insertions(+), 53 deletions(-) diff --git a/amt-desktop-0.0.2.js b/amt-desktop-0.0.2.js index 9f56acd..e09b585 100644 --- a/amt-desktop-0.0.2.js +++ b/amt-desktop-0.0.2.js @@ -75,6 +75,11 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { function strToArr(str) { var arr = new Uint8Array(str.length); for (var i = 0, j = str.length; i < j; ++i) { arr[i] = str.charCodeAt(i); } return arr } obj.ProcessBinaryData = function (data) { + // ###BEGIN###{DesktopRecorder} + // Record the data if needed + if ((obj.recordedData != null) && (obj.recordedHolding !== true)) { obj.recordedData.push(recordingEntry(2, 1, String.fromCharCode.apply(null, new Uint8Array(data)))); } + // ###END###{DesktopRecorder} + // Append to accumulator if (obj.acc == null) { obj.acc = new Uint8Array(data); @@ -121,6 +126,10 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { obj.canvas.canvas.width = obj.rwidth = obj.width = obj.ScreenWidth = accview.getUint16(0); obj.canvas.canvas.height = obj.rheight = obj.height = obj.ScreenHeight = accview.getUint16(2); + // ###BEGIN###{DesktopRecorder} + obj.DeskRecordServerInit = String.fromCharCode.apply(null, new Uint8Array(obj.acc.buffer.slice(0, 24 + namelen))); + // ###END###{DesktopRecorder} + // These are all values we don't really need, we are going to only run in RGB565 or RGB332 and not use the flexibility provided by these settings. // Makes the javascript code smaller and maybe a bit faster. /* @@ -178,6 +187,12 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { if (obj.acc.byteLength < 4) return; obj.state = 100 + accview.getUint16(2); // Read the number of tiles that are going to be sent, add 100 and use that as our protocol state. cmdsize = 4; + + // ###BEGIN###{DesktopRecorder} + // This is the start of a new frame, start recording now if needed. + if (obj.recordedHolding === true) { delete obj.recordedHolding; obj.recordedData.push(recordingEntry(2, 1, String.fromCharCode.apply(null, obj.acc))); } + // ###END###{DesktopRecorder} + break; case 2: // This is the bell, do nothing. cmdsize = 1; @@ -889,5 +904,61 @@ var CreateAmtRemoteDesktop = function (divid, scrolldiv) { return Position; } + // ###BEGIN###{DesktopRecorder} + obj.StartRecording = function () { + if ((obj.recordedData != null) && (obj.DeskRecordServerInit != null)) return false; + + // Take a screen shot and save it to file + var b64image = obj.CanvasId.toDataURL('image/png').split(',')[1]; + + // This is an ArrayBuffer, convert it to a string array + //var binary = '', bytes = new Uint8Array(fileReader.result), length = bytes.byteLength; + //for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } + obj.recordedHolding = true; + obj.recordedData = []; + obj.recordedStart = Date.now(); + obj.recordedSize = 0; + obj.recordedData.push(recordingEntry(1, 0, JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, time: new Date().toLocaleString(), protocol: 200, bpp: obj.bpp }))); // Metadata, 200 = Midstream Intel AMT KVM + obj.recordedData.push(recordingEntry(2, 1, obj.DeskRecordServerInit)); + + /* + // Save a screenshot + var cmdlen = (8 + binary.length); + if (cmdlen > 65000) { + // Jumbo Packet + obj.recordedData.push(recordingEntry(2, 1, ShortToStr(27) + ShortToStr(8) + IntToStr(cmdlen) + ShortToStr(3) + ShortToStr(0) + ShortToStr(0) + ShortToStr(0) + binary)); + } else { + // Normal packet + obj.recordedData.push(recordingEntry(2, 1, ShortToStr(3) + ShortToStr(cmdlen) + ShortToStr(0) + ShortToStr(0) + binary)); + } + */ + return true; + } + + obj.StopRecording = function () { + if (obj.recordedData == null) return; + var r = obj.recordedData; + r.push(recordingEntry(3, 0, 'MeshCentralMCREC')); + delete obj.recordedData; + delete obj.recordedStart; + delete obj.recordedSize; + return r; + } + + function recordingEntry(type, flags, data) { + //console.log('recordingEntry', type, flags, (typeof data == 'number')?data:data.length); + // Header: Type (2) + Flags (2) + Size(4) + Time(8) + // Type (1 = Header, 2 = Network Data), Flags (1 = Binary, 2 = User), Size (4 bytes), Time (8 bytes) + var now = Date.now(); + if (typeof data == 'number') { + obj.recordedSize += data; + return ShortToStr(type) + ShortToStr(flags) + IntToStr(data) + IntToStr(now >> 32) + IntToStr(now & 32); + } else { + obj.recordedSize += data.length; + return ShortToStr(type) + ShortToStr(flags) + IntToStr(data.length) + IntToStr(now >> 32) + IntToStr(now & 32) + data; + } + } + // ###END###{DesktopRecorder} + return obj; } diff --git a/amt-ider-node-0.0.1.js b/amt-ider-node-0.0.1.js index a702e83..cfbfbbf 100644 --- a/amt-ider-node-0.0.1.js +++ b/amt-ider-node-0.0.1.js @@ -6,7 +6,6 @@ function CreateIMRSDKWrapper() { var obj = {}; - var _IMRSDK; var _ImrSdkVersion; var _ref = require('ref'); @@ -22,23 +21,28 @@ function CreateIMRSDKWrapper() { // Callback from the native lib back into js (StdCall) var uintPtr = _ref.refType('uint'); - var OpenHandlerCallBack = _ffi.Callback('int', ['uint', 'uint'], _ffi.FFI_STDCALL, function (clientID, conID) { obj.pendingData[conID] = ''; return 0; }); - var CloseHandlerCallBack = _ffi.Callback('int', ['uint'], _ffi.FFI_STDCALL, function (conID) { obj.pendingData[conID] = ''; return 0; }); + var OpenHandlerCallBack = _ffi.Callback('int', ['uint', 'uint'], _ffi.FFI_STDCALL, function (clientID, conID) { obj.conID = conID; obj.pendingData[conID] = ''; return 0; }); + var CloseHandlerCallBack = _ffi.Callback('int', ['uint'], _ffi.FFI_STDCALL, function (conID) { obj.pendingData[conID] = null; return 0; }); var ReceiveHandlerCallBack = _ffi.Callback('int', ['pointer', uintPtr, 'uint'], _ffi.FFI_STDCALL, function (bufferPtr, lengthPtr, conID) { try { - var bufferLen = lengthPtr.readUInt32LE(0), buffer = _ref.reinterpret(bufferPtr, bufferLen); - lengthPtr.writeUInt32LE(obj.pendingData[conID].length, 0); - for (var i = 0; i < obj.pendingData[conID].length; i++) { buffer[i] = obj.pendingData[conID].charCodeAt(i); } - // ###BEGIN###{IDERDebug} - //console.log('ReceiveHandlerCallBack(conID: ' + conID + ', Len: ' + obj.pendingData[conID].length + ')'); - if (logFile != null) { logFile.write('IDERRECV: ' + rstr2hex(obj.pendingData[conID]) + '\r\n'); } - // ###END###{IDERDebug} - obj.pendingData[conID] = ''; + if (obj.pendingData[conID] == null) { + lengthPtr.writeUInt32LE(0, 0); // Send a close + } else { + var bufferLen = lengthPtr.readUInt32LE(0), buffer = _ref.reinterpret(bufferPtr, bufferLen); + lengthPtr.writeUInt32LE(obj.pendingData[conID].length, 0); + for (var i = 0; i < obj.pendingData[conID].length; i++) { buffer[i] = obj.pendingData[conID].charCodeAt(i); } + // ###BEGIN###{IDERDebug} + //console.log('ReceiveHandlerCallBack(conID: ' + conID + ', Len: ' + obj.pendingData[conID].length + ')'); + if (logFile != null) { logFile.write('IDERRECV: ' + rstr2hex(obj.pendingData[conID]) + '\r\n'); } + // ###END###{IDERDebug} + obj.pendingData[conID] = ''; + } } catch (e) { console.log(e); } return 0; }); var SendHandlerCallBack = _ffi.Callback('int', ['pointer', 'uint', 'uint'], _ffi.FFI_STDCALL, function (ptr, length, conID) { try { + if (obj.client == null) return; var buffer = _ref.reinterpret(ptr, length), str = ''; for (var i = 0; i < length; i++) { str += String.fromCharCode(buffer[i]); } // ###BEGIN###{IDERDebug} @@ -123,7 +127,7 @@ function CreateIMRSDKWrapper() { if (_Setup('imrsdk') == false) { if (_Setup('imrsdk_x64') == false) { return null; } } // IMR_Init - obj.Init = function() { + obj.Init = function () { var version = new _IMRVersion(); var error = _IMRSDK.IMR_Init(version.ref(), 'imrsdk.ini'); if (error == 4) return _ImrSdkVersion; // If already initialized, return previous version information. @@ -133,7 +137,7 @@ function CreateIMRSDKWrapper() { } // IMR_InitEx - obj.InitEx = function(client) { + obj.InitEx = function (client) { var version = new _IMRVersion(); var callbacks = new _SockCallBacks(); callbacks.OpenHandler = OpenHandlerCallBack; @@ -247,7 +251,10 @@ function CreateIMRSDKWrapper() { } // IMR_IDERCloseSession - obj.IDERCloseSessionAsync = function (client_id, func) { _IMRSDK.IMR_IDERCloseSession.async(client_id, func); } + obj.IDERCloseSessionAsync = function (client_id, func) { + //console.log('IDERCloseSessionAsync', client_id); + _IMRSDK.IMR_IDERCloseSession.async(client_id, func); + } // IMR_IDERClientFeatureSupported obj.IDERClientFeatureSupported = function (client_id) { @@ -306,10 +313,12 @@ function CreateIMRSDKWrapper() { } globalIderPendingCalls = 0; +globalIderWrapper = null; +globalIderClientId = null; // Construct a Intel AMT IDER object var CreateAmtRemoteIderIMR = function () { - if (globalIderPendingCalls != 0) { console.log('Incomplete IDER cleanup (' + globalIderPendingCalls + ').'); return null; } // IDER is not ready. + if (globalIderPendingCalls != 0) { console.log('Incomplete IDER cleanup (A, ' + globalIderPendingCalls + ').'); return null; } // IDER is not ready. var _net = require('net'); var _tls = require('tls'); @@ -360,10 +369,11 @@ var CreateAmtRemoteIderIMR = function () { try { if (obj.m.clientid !== undefined) { - try { obj.m.imrsdk.IDERCloseSessionAsync(obj.m.clientid, function (error) { }); } catch (e) { } - delete obj.m.clientid; + try { obj.m.imrsdk.IDERCloseSessionAsync(obj.m.clientid, function (error) { console.log('IDERCloseSessionAsync-Response', error); }); } catch (e) { console.log(e); } + //try { obj.m.imrsdk.RemoveClient(obj.m.clientid); } catch (e) { console.log(e); } + //delete obj.m.clientid; } - obj.m.imrsdk.Close(function (error) { }); + //obj.m.imrsdk.Close(function (error) { }); } catch (e) { } delete obj.m.imrsdk; } @@ -414,7 +424,7 @@ var CreateAmtRemoteIderIMR = function () { } function startIderSession(userConsentFunc) { - if (globalIderPendingCalls != 0) { console.log('Incomplete IDER cleanup (' + globalIderPendingCalls + ').'); return; } + if (globalIderPendingCalls != 0) { console.log('Incomplete IDER cleanup (B, ' + globalIderPendingCalls + ').'); return; } try { //console.log('IDER-Start'); if (obj.m.xtlsoptions && obj.m.xtlsoptions.meshServerConnect) { @@ -452,18 +462,17 @@ var CreateAmtRemoteIderIMR = function () { obj.m.client.on('data', function (data) { //console.log('IDER-RECV(' + data.length + ', ' + obj.receivedCount + '): ' + rstr2hex(data)); - - if (obj.m.imrsdk == null) { return; } + if ((obj.m.imrsdk == null) || (obj.m.imrsdk.pendingData[obj.m.imrsdk.conID] == null)) { return; } if ((obj.receivedCount == 0) && (data.charCodeAt(0) == 0x11) && (data.charCodeAt(1) == 0x05)) { // We got a user consent error, handle it now before IMRSDK.dll can get it. console.log('IDER user consent required.'); - obj.m.imrsdk.pendingData[obj.m.clientid] = String.fromCharCode(0x11, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); // Try to cause a fault. - obj.m.imrsdk.ReadyReadSock(0, function (x, error) { }); + obj.m.imrsdk.pendingData[obj.m.imrsdk.conID] = String.fromCharCode(0x11, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); // Try to cause a fault. + obj.m.imrsdk.ReadyReadSock(obj.m.imrsdk.conID, function (x, error) { }); setTimeout(function () { obj.m.userConsentFunc(startIderSession); }, 500); } else { - if (obj.m.imrsdk.pendingData[obj.m.clientid] == null) { obj.m.imrsdk.pendingData[obj.m.clientid] = data; } else { obj.m.imrsdk.pendingData[obj.m.clientid] += data; } - obj.m.imrsdk.ReadyReadSock(0, function (x, error) { }); + obj.m.imrsdk.pendingData[obj.m.imrsdk.conID] += data; + obj.m.imrsdk.ReadyReadSock(obj.m.imrsdk.conID, function (x, error) { }); } obj.receivedCount += data.length; }); @@ -483,15 +492,19 @@ var CreateAmtRemoteIderIMR = function () { try { //console.log('IDER-StartEx'); obj.m.userConsentFunc = userConsentFunc; - obj.m.imrsdk = CreateIMRSDKWrapper(); - obj.m.imrsdk.InitEx(obj.m.client); - obj.m.imrsdk.RemoveAllClients(); - if (obj.m.amtcertpath) { obj.m.imrsdk.SetCertificateInfo(obj.m.amtcertpath, null, null); } - obj.m.clientid = obj.m.imrsdk.AddClient(obj.m.tls + 1, obj.m.host); + if (globalIderWrapper == null) { + obj.m.imrsdk = globalIderWrapper = CreateIMRSDKWrapper(); + obj.m.imrsdk.InitEx(obj.m.client); + if (obj.m.amtcertpath) { obj.m.imrsdk.SetCertificateInfo(obj.m.amtcertpath, null, null); } + } else { + obj.m.imrsdk = globalIderWrapper; + obj.m.imrsdk.client = obj.m.client; + } + obj.m.clientid = globalIderClientId = obj.m.imrsdk.AddClient(obj.m.tls + 1, 'HOST' + Math.random()); + obj.m.imrsdk.pendingData[obj.m.clientid] = ''; + globalIderPendingCalls++; - //console.log('IDEROpenTCPSessionAsync-call'); var error = obj.m.imrsdk.IDEROpenTCPSessionAsync(obj.m.clientid, obj.m.user, obj.m.pass, obj.m.imgpath, obj.m.isopath, function (error) { - //console.log('IDEROpenTCPSessionAsync-callback(' + error + ')'); globalIderPendingCalls--; if (obj.m.imrsdk == null) return; // We closed already, exit now. if ((error == 38) && (userConsentFunc != undefined)) { obj.m.Stop(); userConsentFunc(startIderSession); return; } @@ -510,7 +523,7 @@ var CreateAmtRemoteIderIMR = function () { } }); } catch (e) { - //console.log(e); + console.log(e); obj.m.Stop(); obj.m.onDialogPrompt(obj.m, { 'html': e }, 1); } diff --git a/index.html b/index.html index f3b32da..c172dd7 100644 --- a/index.html +++ b/index.html @@ -961,7 +961,9 @@ + + @@ -1002,6 +1004,14 @@   --> + + +
+ + +
+ +
@@ -1837,9 +1847,9 @@ // ###BEGIN###{FileSaver} // ###BEGIN###{Desktop} - if (!Q('Desk')['toBlob']) { // On some browsers like IE, we can't save screen shots. Hide the scheenshot/capture buttons. - QV('idx_deskSaveBtn', false); - } + // ###BEGIN###{!Mode-NodeWebkit} + if (!Q('Desk')['toBlob']) { QV('idx_deskSaveBtn', false); }// On some browsers like IE, we can't save screen shots. Hide the sceeenshot/capture buttons. + // ###END###{!Mode-NodeWebkit} // ###END###{Desktop} // ###END###{FileSaver} @@ -1998,9 +2008,7 @@ haltEvent(e); // ###BEGIN###{IDER} var diskimages = true; - console.log(e.dataTransfer.files); for (var i = 0; i < e.dataTransfer.files.length; i++) { - console.log(i, e.dataTransfer.files[i]); if ((e.dataTransfer.files[i].name.toLowerCase().endsWith('.img') == false) && (e.dataTransfer.files[i].name.toLowerCase().endsWith('.iso') == false)) { diskimages = false; } } if ((diskimages == true) && (e.dataTransfer != null) && (currentView > 0) && (currentView < 100) && ((e.dataTransfer.files.length == 1) || (e.dataTransfer.files.length == 2))) { @@ -8062,6 +8070,9 @@ // ###END###{DesktopType} switch (state) { case 0: + // ###BEGIN###{DesktopRecorder} + if (desktop.m.recordedData != null) { deskRecordSession(); } + // ###END###{DesktopRecorder} // desktop.m.ResetScreen(); // ###BEGIN###{DesktopInband} webRtcDesktopReset(); @@ -8287,27 +8298,42 @@ } } + // ###BEGIN###{FileSaver} + // ###BEGIN###{DesktopRecorder} + // Toggle desktop session recording + function deskRecordSession() { + if (desktop == null) return; + if (desktop.m.recordedData == null) { + // Start recording + if ((desktop.State === 3) && (desktop.m.StartRecording())) { Q('DeskRecordButtonImage').src = 'images-commander/icon-film-red.png'; } + } else { + // Stop recording + Q('DeskRecordButtonImage').src = 'images-commander/icon-film.png'; + var d = new Date(), n = 'AmtDesktopSesion-' + currentcomputer['name'] + '-' + d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2) + '-' + ('0' + d.getHours()).slice(-2) + '-' + ('0' + d.getMinutes()).slice(-2); + saveAs(data2blob(desktop.m.StopRecording().join('')), n + '.mcrec'); + } + } + // ###END###{DesktopRecorder} + // ###END###{FileSaver} + // ###BEGIN###{FileSaver} // Save the desktop image to file function deskSaveImage() { if (xxdialogMode || desktop.State != 3) return; var n = 'Desktop', d = new Date(); if (amtsysstate) { n += '-' + amtsysstate['AMT_GeneralSettings'].response['HostName']; } - n += '-' + d.getFullYear() + '-' + ('0'+(d.getMonth()+1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2) + '-' + ('0' + d.getHours()).slice(-2) + '-' + ('0' + d.getMinutes()).slice(-2); - Q('Desk')['toBlob']( - function(blob) { - // ###BEGIN###{!Mode-NodeWebkit} - saveAs(blob, n + '.jpg'); - // ###END###{!Mode-NodeWebkit} - // ###BEGIN###{Mode-NodeWebkit} - var chooser = document.createElement('input'); - chooser.setAttribute('type', 'file'); - chooser.setAttribute('nwsaveas', n + '.jpg'); - chooser.addEventListener('change', function () { require('fs').writeFile(this.value, blob, function () { }); }, false); - chooser.click(); - // ###END###{Mode-NodeWebkit} - } - ); + n += '-' + d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2) + '-' + ('0' + d.getHours()).slice(-2) + '-' + ('0' + d.getMinutes()).slice(-2); + // ###BEGIN###{!Mode-NodeWebkit} + Q('Desk')['toBlob']( function (blob) { saveAs(blob, n + '.jpg'); } ); + // ###END###{!Mode-NodeWebkit} + // ###BEGIN###{Mode-NodeWebkit} + var b64image = document.getElementById('Desk').toDataURL('image/png').split(',')[1]; + var chooser = document.createElement('input'); + chooser.setAttribute('type', 'file'); + chooser.setAttribute('nwsaveas', n + '.png'); + chooser.addEventListener('change', function () { console.log('cc'); require('fs').writeFile(this.value, b64image, 'base64', function () { }); }, false); + chooser.click(); + // ###END###{Mode-NodeWebkit} } // ###END###{FileSaver}