用nodejs写一个yys外挂
为什么要用node来写
曾经用python写过一个自用御魂脚本,作为一个前端码农,就考虑能不能用js实现,js如何来使用天使插件(TSPlug.dll)实现后台操作呢?经google,github发现,已经有大佬实现了轮子winax,能够调用通用COM组件。之后用js写脚本的想法开始付诸行动。
封装TSPlug
考虑为了更方便的调用TSPlug的一些方法,自己动手用ts封装了一个天使插件的库
export default class TSPlug {
private ts: TSInstance;
constructor() {
this.ts = TSPlug.init('ts.tssoft');
}
private static init(COM: string): TSInstance {
try {
return new winax.Object(COM);
} catch {
execSync(`regsvr32 ${
resolve(__dirname, '../lib/TSPlug.dll')}`); // 注册插件
return new winax.Object(COM);
}
}
...
}
封装一个TSPlug的类,包含天使插件的所有方法,详细代码ts.dll,有了通用插件之后开始正式写脚本
开始
我们主要实现的是单人多开刷业原火,御灵,组队双开输御魂
项目的目录结构
yys-robot
├── src
│ └── app
│ ├── actions
│ │ ├── bind-window.ts
│ │ ├── find-window.ts
│ │ └── get-screen-config.ts
│ ├── config
│ │ ├── config.ts
│ │ └── shared.ts
│ ├── interfaces
│ │ └── interfaces.ts
│ ├── mode
│ │ ├── single.mode.ts
│ │ └── team.mode.ts
│ ├── workers // 工作进程
│ │ ├── single.worker.js
│ │ ├── single.worker.ts
│ │ ├── team-driver.worker.js
│ │ ├── team-driver.worker.ts
│ │ ├── team-fighter.worker.js
│ │ └── team-fighter.worker.ts
│ ├── app.single.ts // 单人模式入口
│ ├── app.team.ts // 组队模式入口
│ └── app.ts // 程序入口
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── single.bat // 批处理命令快速启动单人模式
├── team.bat // .....快速启动组队模式
├── tsconfig.json
└── tslint.json
config基本配置文件
//config.ts
export const screenConfig = {
color: {
yellow: 'f3b25e', // 黄色的挑战按钮
auto: 'f8f3e0', // 自动战斗
fighterAutoAccept: 'edc791', // 组队模式打手自动接受邀请
normalAccept: '54b05f', // 组队模式常规邀请
blankBattle: '2b2b2b', // 空白的挑战按钮
reward: 'dd7260', // 悬赏关闭按钮
team: 'e9cd73', // 组队挑战按钮
defaultInvitation: '725f4d', // 默认邀请按钮,
fontCooperative: 'f8f3e0', // 协战队伍文字颜色,
fighterReady: '221611'
},
position: {
// range 为随机点击区域的范围
singleBattle: [807, 422],
singleBattleRange: [807, 807 + 74, 422, 422 + 17],
teamBattle: [1091, 576],
teamBattleRange: [1070, 1105, 572, 630],
auto: [71, 577],
settlement: [980, 1030, 225, 275],
teamSettlement: [189, 333, 23, 76],
continueInviteButton: [724, 396],
continueInviteButtonRange: [724 - 5, 724 + 5, 396 - 5, 396 + 5],
rejectRewardButton: [750, 458],
rejectRewardButtonRange: [745, 755, 453, 463],
defaultInvitationButton: [499, 321],
defaultInvitationButtonRange: [489, 509, 311, 331],
blankBattleButton: [1067, 585],
autoAcceptButtonRange: [16, 366, 122, 465], // 自动接受按钮出现的区域,方便findColor找色
fontCooperativeRange: [68, 204, 14, 60], // 协战队伍文字出现的区域,
fighterReadyPosition: [985, 587] // 辅助位置,组队打手进入了备战界面
}
};
// shared.ts 全局共享对象
import TSPlug from 'ts.dll';
import {
Shared} from '../interfaces/interfaces';
export const shared: Shared = {
original: new TSPlug(),
handles: [] // all window handle
};
actions存放基本通用方法
//find-window.ts 查找窗口句柄
import TSPlug from 'ts.dll';
import {
shared} from '../config/shared';
export function findWindow(ts: TSPlug) {
//通过进程id获取窗口句柄,返回逗号分隔的字符串
const windowHandelString = ts.enumWindowByProcess('client.exe', '', '', 16); // onmyoji.exe 进程id可能会不一样
if (windowHandelString) {
const windowHandelRaw = windowHandelString.split(',');
// 存储全局共享的handle
const windowHandel = shared.handles = windowHandelRaw.map(v => Number(v));
return [...windowHandel];
} else {
console.log('not found window handle');
return [];
}
}
// bind-window.ts 通过窗口句柄绑定窗口实现后台运行
import TSPlug from 'ts.dll';
export function bindWindow(handle: number) {
const ts = new TSPlug();
const ret = ts.bindWindow(handle, 'dx2', 'windows', 'windows', 0);
if (ret === 1) {
console.log('bind window success');
return ts;
}
throw {
message: 'bind window failed'};
}
// get-screen-config.ts 根据传入的不同分辨率的比值返回不同的坐标配置(暂时只测试了480*852分辨率,默认分辨率自行修改代码测试)
import {
screenConfig} from '../config/config';
export function getScreenConfig(resolution: 1 | 1.333333333 = 1.333333333) {
if (resolution === 1) {
return screenConfig;
}
return transConfig(resolution);
}
function transConfig(resolution: 1 | 1.333333333) {
let i: keyof typeof screenConfig.position;
for (i in screenConfig.position) {
if (screenConfig.position.hasOwnProperty(i)) {
const item = screenConfig.position[i];
screenConfig.position[i] = item.map(v => v / resolution);
}
}
screenConfig.color.auto = 'f4efdc';
return screenConfig;
}
mode单人或双人模式
// single.mode.ts
import {
fork} from 'child_process';
import {
shared} from '../config/shared';
import {
resolve} from 'path';
// 单人模式,每一个窗口应该独立运行,通过child_process 创建子进程
// node子进程不能共享主进程的object,但是可以共享数字,向子进程传输全局共享的handle
export function single() {
for (const handle of shared.handles) {
const worker = fork(resolve(__dirname, '../workers/single.worker.js'));
worker.send(handle);
}
}
// team.mode.ts 组队模式,主逻辑不需要子进程,只需要司机执行一些操作,结算的时候需要分辨创建司机和打手两个子进程来执行不同的操作
import TSPlug from 'ts.dll';
import {
shared} from '../config/shared';
import {
bindWindow} from '../actions/bind-window';
import {
getScreenConfig} from '../actions/get-screen-config';
import {
Area} from 'ts.dll/@types/modules/interface';
import {
fork} from 'child_process';
import {
resolve as pathResolve} from 'path';
export async function team() {
if (shared.handles.length < 2) throw {
message: 'window handles less than 2'};
// ======================各种配置=========================
const screenConfig = getScreenConfig();
const {
color, position} = screenConfig;
const {
team, auto, reward} = color;
const {
teamBattleRange, teamBattle, auto: autoButton, settlement, rejectRewardButton, rejectRewardButtonRange} = position;
const teamBattleArea = {
x1: teamBattleRange[0],
x2: teamBattleRange[1],
y1: teamBattleRange[2],
y2: teamBattleRange[3]
};
const rejectRewardArea = {
x1: rejectRewardButtonRange[0],
x2: rejectRewardButtonRange[1],
y1: rejectRewardButtonRange[2],
y2: rejectRewardButtonRange[3]
};
// ===========================================
let driver = bindWindow(shared.handles