| const fs = require('fs'); | |||||
| const spawn = require('child_process').spawn; | |||||
| const spawnSync = require('child_process').spawnSync; | |||||
| const path = require('path'); | |||||
| const scanf = require('scanf'); | |||||
| const HTMLParser = require('node-html-parser'); | |||||
| const hw = require(path.join(__dirname, 'GTBOT.node')); | |||||
| /** | |||||
| * option: { | |||||
| * nox_path: E:/nox/Nox, | |||||
| * open_count: 2, | |||||
| * close_time: 1000(ms) | |||||
| * } | |||||
| */ | |||||
| class GT_Nox_Command{ | |||||
| /** | |||||
| * @param {String} noxName 夜神內部Id e.g. Nox_1,Nox_2 | |||||
| * @param {String} noxPath 夜神的安裝位置, 預設為 C:/Program Files (x86)/Nox | |||||
| */ | |||||
| constructor(titleTable,option){ | |||||
| this.titleToNoxIdTable = titleTable; | |||||
| if(typeof option !== "object") option = {}; | |||||
| this.noxPath = option.nox_path?option.nox_path:"C:/Program Files (x86)/Nox"; | |||||
| this.openCount = option.open_count?option.open_count:1; | |||||
| this.closeTime = option.close_time?option.close_time:1000; | |||||
| this.nox_command_file = path.join(this.noxPath,"bin","Nox.exe"); | |||||
| this.errorExec = []; | |||||
| } | |||||
| nox_command(titleName,option){ | |||||
| let argv_arr = ["-clone:" + this.titleToNoxIdTable[titleName] ]; | |||||
| if(typeof option === "object"){ | |||||
| for(let i in option){ | |||||
| let temp = "-" + i + ":" + option[i]; | |||||
| argv_arr[argv_arr.length] = temp; | |||||
| } | |||||
| } | |||||
| else if(typeof option === "string"){ | |||||
| argv_arr[argv_arr.length] = option; | |||||
| } | |||||
| let cmd = spawn(this.nox_command_file, argv_arr, { | |||||
| detached: true | |||||
| }); | |||||
| cmd.stdout.on('data', (data) => { | |||||
| // console.log(`stdout: \n${data}`); | |||||
| }); | |||||
| cmd.stderr.on('data', (data) => { | |||||
| // console.log(`stderr: ${data}`); | |||||
| }); | |||||
| cmd.on('close', (code) => { | |||||
| // console.log(`child process exited with code $[code]`); | |||||
| }); | |||||
| } | |||||
| static checkOpen(title,cb){ | |||||
| let count = 0; | |||||
| let intervalTemp = setInterval(function(){ | |||||
| let mainW = hw.findWindowEx(0, 0, "Qt5QWindowIcon" , title); | |||||
| if(mainW){ | |||||
| // init 時會有一個 title 是""的東西 | |||||
| let check1 = hw.findWindowEx(mainW, 0, "Qt5QWindowIcon" , ''); | |||||
| let check2 = hw.findWindowEx(mainW, 0, "Qt5QWindowIcon" , 'default_title_barWindow'); | |||||
| if(check1 === check2){ | |||||
| if(++count > 3){ | |||||
| clearInterval(intervalTemp); | |||||
| cb(); | |||||
| } | |||||
| } | |||||
| } | |||||
| },1000); | |||||
| } | |||||
| open_process(titleName){ | |||||
| if(this.titleToNoxIdTable[titleName] === undefined){ | |||||
| this.errorExec[this.errorExec.length] = titleName; | |||||
| return; | |||||
| } | |||||
| this.nox_command(titleName); | |||||
| GT_Nox_Command.checkOpen(titleName,function(){ | |||||
| this.finish_open(); | |||||
| }.bind(this)); | |||||
| } | |||||
| finish_open(){ | |||||
| if(this.open_arr.length <= 0){ | |||||
| let msg = {}; | |||||
| if(this.errorExec.length > 0){ | |||||
| msg.err = this.errorExec.slice(0);; | |||||
| } | |||||
| this.finish_open_cb(msg); | |||||
| this.errorExec = []; | |||||
| return; | |||||
| } | |||||
| else{ | |||||
| let nextOpen = this.open_arr.shift(); | |||||
| this.open_process(nextOpen); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 開啟模擬器 | |||||
| * @param {Array || String} titleNameArr 要開啟哪些APK | |||||
| * @param {Function} cb 開完了,你要做啥 | |||||
| * option{ | |||||
| * cpu:1, | |||||
| * memory: 960(or 1024), | |||||
| * resolution: '320x240', | |||||
| * dpi: 120, | |||||
| * root:false | |||||
| * } | |||||
| */ | |||||
| openNox(titleNameArr,cb){ | |||||
| let titleArr; | |||||
| if( Array.isArray(titleNameArr) ){ | |||||
| titleArr = titleNameArr; | |||||
| } | |||||
| else if(typeof titleNameArr === "string"){ | |||||
| titleArr = [titleNameArr] | |||||
| } | |||||
| this.open_arr = titleArr; | |||||
| this.finish_open_cb = cb; | |||||
| for(let i=0;i < this.openCount;i++){ | |||||
| let nextOpen = this.open_arr.shift(); | |||||
| this.open_process(nextOpen,i); | |||||
| if(this.open_arr.length === 0){ | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| colse_process(closeTitle){ | |||||
| if(this.titleToNoxIdTable[closeTitle] === undefined){ | |||||
| this.errorExec[this.errorExec.length] = closeTitle; | |||||
| return; | |||||
| } | |||||
| this.nox_command(closeTitle,"-quit"); | |||||
| } | |||||
| /** | |||||
| * 關閉模擬器 | |||||
| * @param {Array || String} titleNameArr 要關哪些APK | |||||
| * @param {Function} cb 關完了,你要做啥 | |||||
| */ | |||||
| closeNox(titleNameArr,cb){ | |||||
| let titleArr; | |||||
| if( Array.isArray(titleNameArr) ){ | |||||
| titleArr = titleNameArr; | |||||
| } | |||||
| else if(typeof titleNameArr === "string"){ | |||||
| titleArr = [titleNameArr] | |||||
| } | |||||
| // console.log(cb); | |||||
| this.activeFlage = true; | |||||
| let closeCount = 1; | |||||
| this.colse_process(titleArr[0]); | |||||
| let closeItl = setInterval(function(){ | |||||
| this.colse_process(titleArr[closeCount]); | |||||
| closeCount++; | |||||
| if(closeCount >= titleArr.length){ | |||||
| clearInterval(closeItl); | |||||
| let msg = {}; | |||||
| if(this.errorExec.length > 0){ | |||||
| msg.err = this.errorExec.slice(0);; | |||||
| } | |||||
| this.errorExec = []; | |||||
| if(typeof cb === 'function'){ | |||||
| cb(msg); | |||||
| } | |||||
| } | |||||
| }.bind(this),this.closeTime); | |||||
| } | |||||
| restartNox(titleNameArr,cb){ | |||||
| let titleArr; | |||||
| if( Array.isArray(titleNameArr) ){ | |||||
| titleArr = titleNameArr; | |||||
| } | |||||
| else if(typeof titleNameArr === "string"){ | |||||
| titleArr = [titleNameArr] | |||||
| } | |||||
| this.closeNox(titleArr,function(){ | |||||
| setTimeout(()=>{ | |||||
| this.openNox(titleArr,cb); | |||||
| },5000); | |||||
| }.bind(this)); | |||||
| } | |||||
| /** | |||||
| * 安裝APK | |||||
| * @param {String} apk_path apk 的絕對位置 | |||||
| */ | |||||
| install_apk(apk_path){ | |||||
| if(typeof apk_path !== "string"){ | |||||
| console.log("你的apk path呢!"); | |||||
| return; | |||||
| } | |||||
| this.nox_command("'-apk:" + apk_path + "'"); | |||||
| } | |||||
| /** | |||||
| * 執行某個App,預設開托蘭 | |||||
| * @param {string} appId app的唯一Id | |||||
| */ | |||||
| run_app(appId){ | |||||
| if(appId === undefined){ | |||||
| appId = "com.asobimo.toramonline"; | |||||
| } | |||||
| this.nox_command({package:appId}); | |||||
| } | |||||
| } | |||||
| class adb_ctrl{ | |||||
| constructor(option){ | |||||
| this.nox_path = option.nox_path?option.nox_path:"C:/Program Files (x86)/Nox"; | |||||
| this.adb_file = path.join(this.nox_path,"bin","nox_adb.exe"); | |||||
| this.setMultiNoxList.call(this); | |||||
| // title 對應 Nox_id | |||||
| /** | |||||
| * { | |||||
| * atk01: Nox_1 | |||||
| * } | |||||
| */ | |||||
| // this.nox_table = {}; | |||||
| // port 對應 title | |||||
| /** | |||||
| * { | |||||
| * 61025: Nox_1 | |||||
| * } | |||||
| */ | |||||
| // this.port_table = {}; | |||||
| this.nox_cmd = new GT_Nox_Command(this.nox_table,option); | |||||
| // this.connect(); | |||||
| } | |||||
| openNox(titleArr,cb){ | |||||
| this.nox_cmd.openNox.call(this.nox_cmd,titleArr,cb); | |||||
| } | |||||
| closeNox(titleArr,cb){ | |||||
| this.nox_cmd.closeNox.call(this.nox_cmd,titleArr,cb); | |||||
| } | |||||
| restartNox(titleArr,cb){ | |||||
| this.nox_cmd.restartNox.call(this.nox_cmd,titleArr,cb); | |||||
| } | |||||
| exec_adb_cb(argv,cb){ | |||||
| const status = spawn(this.adb_file, argv); | |||||
| status.stdout.setEncoding('utf8'); | |||||
| status.stderr.setEncoding('utf8'); | |||||
| let stdout = ""; | |||||
| let err = false; | |||||
| let errInfo = ""; | |||||
| status.stdout.on('data', (data) => { | |||||
| stdout += (data + "||"); | |||||
| }); | |||||
| status.stderr.on('data', (data) => { | |||||
| errInfo += (data + "||"); | |||||
| }); | |||||
| status.on('close', (code) => { | |||||
| cb({ | |||||
| err: err, | |||||
| errInfo: errInfo, | |||||
| data: stdout | |||||
| }); | |||||
| }); | |||||
| } | |||||
| exec_adb(argv){ | |||||
| return new Promise( (resolve, reject) => { | |||||
| const status = spawn(this.adb_file, argv); | |||||
| status.stdout.setEncoding('utf8'); | |||||
| status.stderr.setEncoding('utf8'); | |||||
| let stdout; | |||||
| status.stdout.on('data', (data) => { | |||||
| stdout = data; | |||||
| // console.log(data); | |||||
| }); | |||||
| status.stderr.on('data', (data) => { | |||||
| // console.log("err",data); | |||||
| resolve({ | |||||
| err: true, | |||||
| data: data | |||||
| }); | |||||
| }); | |||||
| status.on('close', (code) => { | |||||
| resolve({ | |||||
| err: false, | |||||
| data:stdout | |||||
| }); | |||||
| }); | |||||
| }); | |||||
| } | |||||
| delay(time){ | |||||
| return new Promise( (resolve, reject) => { | |||||
| setTimeout(function(){ | |||||
| resolve(); | |||||
| },time); | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * 找multiNox 路徑 | |||||
| */ | |||||
| setMultiNoxList(){ | |||||
| let multi_list; | |||||
| let multi_file = path.join(process.env.LOCALAPPDATA,'MultiPlayerManager','multiplayer.xml'); | |||||
| try{ | |||||
| multi_list = fs.readFileSync(multi_file,{encoding:"utf-8"}); | |||||
| } | |||||
| catch(e){ | |||||
| console.log(e); | |||||
| console.log("找不到夜神多工器啦 冠霆要你滾"); | |||||
| scanf("%d"); | |||||
| } | |||||
| if(multi_list === undefined){ | |||||
| return; | |||||
| } | |||||
| let document = HTMLParser.parse(multi_list); | |||||
| let Instance = document.querySelectorAll("Instance"); | |||||
| this.nox_table = {}; | |||||
| this.port_table = {}; | |||||
| this.title_to_device_table = {}; | |||||
| for(let i=0;i < Instance.length;i++){ | |||||
| let temp = Instance[i].attributes; | |||||
| if(temp.id === "Nox_0"){ | |||||
| temp.id = "nox"; | |||||
| } | |||||
| this.nox_table[temp.name] = temp.id; | |||||
| let yee = fs.readFileSync(this.nox_path + '/bin/BignoxVMS/' + temp.id + '/' + temp.id + '.vbox',{encoding:"utf-8"}); | |||||
| let port = yee.split('<Forwarding name="port2" proto="1" hostip="127.0.0.1" hostport="')[1].slice(0,5); | |||||
| this.port_table[port] = temp.name; | |||||
| this.title_to_device_table[temp.name] = port; | |||||
| } | |||||
| // console.log(this.nox_table); | |||||
| // console.log(this.port_table); | |||||
| } | |||||
| /** | |||||
| * adb 初始化, adb 操作時,必做喔 | |||||
| */ | |||||
| async adb_init(){ | |||||
| // var killServer = spawnSync(this.adb_file, ["kill-server"] ,{encoding: "utf-8"}); | |||||
| let killServer = await this.exec_adb(["kill-server"]); | |||||
| if(killServer.err){ | |||||
| if(killServer.data.indexOf("server not running") === -1){ | |||||
| console.log("kill出錯啦~"); | |||||
| console.log(killServer); | |||||
| scanf("%d"); | |||||
| return "err"; | |||||
| } | |||||
| } | |||||
| let startServer = await this.exec_adb( ["start-server"] ); | |||||
| if(startServer.err){ | |||||
| console.log("start出錯啦~"); | |||||
| console.log(startServer); | |||||
| scanf("%d"); | |||||
| return "err"; | |||||
| } | |||||
| console.log("初始化完成~ 天佑冠霆啦!!"); | |||||
| } | |||||
| scanNox(){ | |||||
| return new Promise( (resolve, reject) => { | |||||
| let count = 0; | |||||
| let max = Object.keys(this.port_table).length; | |||||
| // console.log("scan囉"); | |||||
| // console.log(Object.keys(this.port_table).length); | |||||
| function afterScan(data){ | |||||
| count++; | |||||
| // console.log(count); | |||||
| // console.log(data); | |||||
| if(max === count){ | |||||
| resolve(); | |||||
| } | |||||
| } | |||||
| for(let i in this.port_table){ | |||||
| this.exec_adb_cb( ["connect","127.0.0.1:" + i] ,afterScan); | |||||
| } | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * 連結目前所有已開的夜神模擬器 | |||||
| */ | |||||
| async connect(){ | |||||
| await this.adb_init(); | |||||
| if(this.port_table === undefined){ | |||||
| return "err"; | |||||
| } | |||||
| await this.scanNox(); | |||||
| var devices = await this.exec_adb( ["devices"] ); | |||||
| // console.log(devices); | |||||
| if(devices.err){ | |||||
| console.log("devices出錯啦~"); | |||||
| console.log(devices); | |||||
| scanf("%d"); | |||||
| return "err"; | |||||
| } | |||||
| let all_devices = devices.data.split("List of devices attached\r\n")[1]; | |||||
| let devices_list = all_devices.split("\tdevice\r\n"); | |||||
| devices_list = devices_list.reverse().slice(1); | |||||
| this.devices_list = devices_list; | |||||
| this.devices_count = devices_list.length; | |||||
| this.installed_count = 0; | |||||
| // console.log(devices_list); | |||||
| // console.log("你要更新的裝置數量:" + this.devices_count); | |||||
| } | |||||
| async disconnect(){ | |||||
| let killServer = await this.exec_adb(["kill-server"]); | |||||
| if(killServer.err){ | |||||
| if(killServer.data.indexOf("server not running") === -1){ | |||||
| console.log("kill出錯啦~"); | |||||
| console.log(killServer); | |||||
| scanf("%d"); | |||||
| return "err"; | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 停止toram跟cloneApp 的App | |||||
| */ | |||||
| async close_toram(titleName){ | |||||
| let device = this.title_to_device_table[titleName]; | |||||
| if(device === undefined) return {err: "noThis"}; | |||||
| // for(let i=0; i < this.devices_count;i++){ | |||||
| // let status = await this.exec_adb( ["-s", this.devices_list[i] ,"shell","dumpsys activity | grep top-activity"] ); | |||||
| let status = await this.exec_adb( ["-s", "127.0.0.1:" + device ,"shell","am","force-stop","com.asobimo.toramonline"] ); | |||||
| status = await this.exec_adb( ["-s", "127.0.0.1:" + device ,"shell","am","force-stop","com.zhuowang.cloneapp"] ); | |||||
| return {}; | |||||
| // } | |||||
| // this.nox_cmd.run_app("com.zhuowang.cloneapp:platform.gameplugin.P00"); | |||||
| // this.nox_cmd.run_app(); | |||||
| } | |||||
| } | |||||
| module.exports = adb_ctrl; | |||||
| // let aa = new adb_ctrl({ | |||||
| // nox_path: "F:/Nox", | |||||
| // open_count: 1, | |||||
| // }); | |||||
| // let atkArr = []; | |||||
| // for(let i=1;i< 3;i++){ | |||||
| // atkArr[atkArr.length] = "atk0" + i; | |||||
| // } | |||||
| // async function yee(gtadb){ | |||||
| // // await gtadb.adb_init.call(gtadb); | |||||
| // await gtadb.connect(); | |||||
| // await gtadb.close_toram("atk01"); | |||||
| // await gtadb.close_toram("atk02"); | |||||
| // } | |||||
| // yee(aa); |