//Authour: Dustin Harris //GitHub: https://github.com/DevL0rd const { spawn } = require("child_process"); const DB = require('./Devlord_modules/DB.js'); const Struct = require('struct'); const net = require('net'); const robot = require("robotjs"); const VGen = require("vgen-xbox") const vgen = new VGen(); const cc = require('./Devlord_modules/conColors.js'); const cs = require('./Devlord_modules/conSplash.js'); var ip = "0.0.0.0" var quality = 5; var screenSize = robot.getScreenSize(); var sheight = screenSize.height; var swidth = screenSize.width; var hidStreamClient = new net.Socket(); function connect() { hidStreamClient.connect({ host: ip, port: 2223 }); } var controllerId = 1; var ffmpegProcess; var ffmpegAudioProcess; hidStreamClient.on('error', function (ex) { if (ex) { console.log("Could not connect to Switch. Connection timed out..."); setTimeout(connect, 1000); } }); function plugControllerIn() { console.log("Plugging in virtual controller..."); try { controllerId = vgen.pluginNext(); console.log("Plugging in as controller " + controllerId + "."); } catch (e) { console.log("Could not plug in virtual controller. Make sure the driver is installed."); setTimeout(plugControllerIn, 3000); } } function startAudioProcess() { ffmpegAudioProcess = spawn( "./lib/ffmpeg.exe", ["-y", "-f", "avfoundation", "-i", "audio=virtual-audio-capturer", "-af", "equalizer=f=100:t=h:width=200:g=-64", "-f", "s16le", "-ar", "16000", "-ac", "2", "-c:a", "pcm_s16le", "udp://" + ip + ":2224?pkt_size=640"], { detached: false } ); ffmpegAudioProcess.stdout.on("data", data => { //console.log(`${data}`); }); ffmpegAudioProcess.stderr.on('data', (data) => { console.error(`ffmpegAudioProcessErr: ${data}`); }); ffmpegAudioProcess.on('close', (code) => { console.log(`ffmpegAudioProcess process exited with code ${code}`); }); } function startVideoProcess() { ffmpegProcess = spawn( "./lib/ffmpeg.exe", ["-probesize", "10M", "-f", "gdigrab", "-framerate", "60", "-video_size", swidth + "x" + sheight, "-offset_x", "0", "-offset_y", "0", "-i", "desktop", "-f", "h264", "-vf", "scale=1280x720", "-preset", "ultrafast", "-tune", "zerolatency", "-pix_fmt", "yuv420p", "-profile:v", "baseline", "-x264-params", "\"nal-hrd=cbr\"", "-b:v", quality + "M", "-minrate", quality + "M", "-maxrate", quality + "M", "-bufsize", "2M", "tcp://" + ip + ":2222"], { detached: false } ); ffmpegProcess.stdout.on("data", data => { // console.log(`${data}`); }); ffmpegProcess.stderr.on('data', (data) => { // console.error(`ffmpegProcessErr: ${data}`); }); ffmpegProcess.on('close', (code) => { console.log(`ffmpegProcess process exited with code ${code}`); }); } hidStreamClient.on('connect', function () { console.log('Connected to Switch!'); plugControllerIn(); startVideoProcess(); startAudioProcess(); }); var switchHidBuffer = new Buffer.alloc(0); function parseInputStruct(buff) { var input = Struct() .word64Ule('HeldKeys') .word16Ule('LJoyX') .word16Ule('LJoyY') .word16Ule('RJoyX') .word16Ule('RJoyY') .word16Ule('touchX') .word16Ule('touchY') input._setBuff(buff); return input; }; function isOdd(int) { return (int & 1) === 1; } function heldKeysBitmask(HeldKeys) { return { A: isOdd(HeldKeys >> 0), B: isOdd(HeldKeys >> 1), X: isOdd(HeldKeys >> 2), Y: isOdd(HeldKeys >> 3), LS: isOdd(HeldKeys >> 4), RS: isOdd(HeldKeys >> 5), L: isOdd(HeldKeys >> 6), R: isOdd(HeldKeys >> 7), ZL: isOdd(HeldKeys >> 8), ZR: isOdd(HeldKeys >> 9), Plus: isOdd(HeldKeys >> 10), Minus: isOdd(HeldKeys >> 11), Left: isOdd(HeldKeys >> 12), Up: isOdd(HeldKeys >> 13), Right: isOdd(HeldKeys >> 14), Down: isOdd(HeldKeys >> 15) } } var heldKeysOld; var LJoyXold; var LJoyYold; var RJoyXold; var RJoyYold; var touchXold; var touchYold; var mouseIsDown = false; hidStreamClient.on('data', function (data) { //logDataStream(data) //buffer2 = new Buffer.from(data, "hex"); switchHidBuffer = new Buffer.from(data); var hid = parseInputStruct(switchHidBuffer) var heldKeys = hid.get("HeldKeys"); var touchX = hid.get("touchX"); var touchY = hid.get("touchY"); var LJoyX = hid.get("LJoyX"); var LJoyY = hid.get("LJoyY"); var RJoyX = hid.get("RJoyX"); var RJoyY = hid.get("RJoyY"); if (LJoyX != LJoyXold || LJoyY != LJoyYold) { var nljx; var nljy; if (LJoyX) { nljx = LJoyX / 32767.5 } if (nljx > 1) { nljx = 2 - nljx nljx = -nljx } if (LJoyY) { nljy = LJoyY / 32767.5 } if (nljy > 1) { nljy = 2 - nljy nljy = -nljy } vgen.setAxisL(controllerId, nljx, nljy); LJoyXold = LJoyX; LJoyYold = LJoyY; } if (RJoyX != RJoyXold || RJoyY != RJoyYold) { var nrjx; var nrjy; if (RJoyX) { nrjx = RJoyX / 32767.5 } if (nrjx > 1) { nrjx = 2 - nrjx nrjx = -nrjx } if (RJoyY) { nrjy = RJoyY / 32767.5 } if (nrjy > 1) { nrjy = 2 - nrjy nrjy = -nrjy } vgen.setAxisR(controllerId, nrjx, nrjy); RJoyXold = RJoyX; RJoyYold = RJoyY; } if (heldKeys != heldKeysOld) { var inputStates = heldKeysBitmask(heldKeys); //Button mapping vgen.setButton(controllerId, vgen.Buttons.B, inputStates.A); vgen.setButton(controllerId, vgen.Buttons.A, inputStates.B); vgen.setButton(controllerId, vgen.Buttons.X, inputStates.Y); vgen.setButton(controllerId, vgen.Buttons.Y, inputStates.X); vgen.setButton(controllerId, vgen.Buttons.BACK, inputStates.Minus); vgen.setButton(controllerId, vgen.Buttons.START, inputStates.Plus); vgen.setButton(controllerId, vgen.Buttons.LEFT_SHOULDER, inputStates.L); vgen.setButton(controllerId, vgen.Buttons.RIGHT_SHOULDER, inputStates.R); vgen.setButton(controllerId, vgen.Buttons.LEFT_THUMB, inputStates.LS); vgen.setButton(controllerId, vgen.Buttons.RIGHT_THUMB, inputStates.RS); //Trigger Mapping if (inputStates.ZL) { vgen.setTriggerL(controllerId, 1); } else { vgen.setTriggerL(controllerId, 0); } if (inputStates.ZR) { vgen.setTriggerR(controllerId, 1); } else { vgen.setTriggerR(controllerId, 0); } //Dpad mapping if (inputStates.Up || inputStates.Down || inputStates.Left || inputStates.Right) { if (inputStates.Up) { if (inputStates.Left || inputStates.Right) { if (inputStates.Left) { vgen.setDpad(controllerId, vgen.Dpad.UP_LEFT); } else { vgen.setDpad(controllerId, vgen.Dpad.UP_RIGHT); } } else { vgen.setDpad(controllerId, vgen.Dpad.UP); } } else if (inputStates.Down) { if (inputStates.Left || inputStates.Right) { if (inputStates.Left) { vgen.setDpad(controllerId, vgen.Dpad.DOWN_LEFT); } else { vgen.setDpad(controllerId, vgen.Dpad.DOWN_RIGHT); } } else { vgen.setDpad(controllerId, vgen.Dpad.DOWN); } } else if (inputStates.Left) { vgen.setDpad(controllerId, vgen.Dpad.LEFT); } else if (inputStates.Right) { vgen.setDpad(controllerId, vgen.Dpad.RIGHT); } } else { vgen.setDpad(controllerId, vgen.Dpad.NONE); } heldKeysOld = heldKeys; } if (touchX != touchXold || touchY != touchYold) { if (touchX && touchY) { touchX -= 15; touchY -= 15; var posXPercent = touchX / 1249 var posYPercent = touchY / 689 var posX = Math.floor(swidth * posXPercent) var posY = Math.floor(sheight * posYPercent) robot.moveMouse(posX, posY); if (!mouseIsDown) { robot.mouseToggle("down"); mouseIsDown = true; } touchTime++; } else { if (mouseIsDown) { robot.mouseToggle("up"); mouseIsDown = false; } touchTime = 0; } touchXold = touchX; touchYold = touchY; } }); hidStreamClient.on('end', function () { console.log('hidStreamClient Disconnected.'); try { vgen.unplug(controllerId); } catch (error) { } ffmpegProcess.kill(); ffmpegAudioProcess.kill(); setTimeout(connect, 1000); }); var args = process.argv.slice(" "); if (args.length > 1) { if (args.includes("/ip") && args[args.indexOf("/ip") + 1]) { ip = args[args.indexOf("/ip") + 1]; console.log('Waiting for connection...'); if (args.includes("/q") && args[args.indexOf("/q") + 1]) { quality = args[args.indexOf("/q") + 1]; } else { console.log('Error: Usage NXStreamer.exe ip 0.0.0.0 q 5'); } connect(); } else { console.log('Error: Usage NXStreamer.exe ip 0.0.0.0 q 5'); } } else { console.log('Error: Usage NXStreamer.exe ip 0.0.0.0 q 5'); }