| @@ -0,0 +1,487 @@ | |||
| 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); | |||