`
micheal19840929
  • 浏览: 161770 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

重磅推出诛仙辅助软件第二波:Java版按键精灵

    博客分类:
  • J2SE
阅读更多

前言:上次推出诛仙答题辅助软件用起来不错(想上十名榜不难,YY下),不过在后来我自己使用过程中发现程序存在BUG,就是启用后,如果你不是按小键盘的12345而按鼠标右键,程序就不能使用了,应该是进入逻辑循环,根本原因是我的程序架构上存在问题,所以针对上次的教训本次推出的第二波辅助软件采用全新的架构:“模式架构”(名字要响亮,:)),这种模式能从根本解决之前的那类逻辑问题,而且能够很明朗的进行业务分析。

      回到正题,本次推出的辅助软件是一个精简的按键精灵,不过麻雀虽小,五脏俱全,基本的功能还是有的,虽然没有可视化,但并不影响使用(使用XML作为脚本文件,可以很方便进行修改)。先说应用吧,现在的玩诛仙的人很多都会挂机,那么挂机就要用的按键精灵。一般人都使用网上已有的那个精灵,然而说不定哪天老池来一次精灵封杀,到时给你一个封号几万小时,你哭都来不及。所以还是自力更生,才能丰衣足食啊。

      本次推出的精灵主要功能有:

1、支持鼠标、键盘按键记录
2、支持脚本生成及脚本导入

3、只支持单个计时器(不知道多个计时器有没必要)

4、只记录鼠标的左键事件,并且只记录鼠标click事件,不记录拖动事件

5、只记录键盘按键的按下事件

6、保存脚本的时间以秒为单位(带一位小数)

 

     软件的使用方法如下:

1、启动run.bat,系统打开命令提示符并运行软件,开始是进行菜单选择(录制模式与模拟模式),可以按F12退出软件

2、如果选择录制模式则系统开始录制你的鼠标及键盘操作(具体参考功能列表),这时可以按F12停止录制并返回菜单选择

3、退出录制模式后,软件会将你的本次操作列表在软件目录下保存成脚本文件,名字为script.xml

4、如果选择模拟模式,软件会在它的目录下导入script.xml脚本文件并开始循环模拟操作列表中的操作。可以按F12停止模拟并返回菜单选择。

 

     进入正文,先来看看我们的项目架构,看图:

项目架构

因为本软件涉及的类比较多,所以对于之前有讲过的类(包括KeyboardHookEventListener.java、MouseHookEventListener.java、Task.java、TaskThread.java)这里就不做详述了,需要的可以参考我之前的文章:Java语言的Hook实现

下面我们先来看下所谓的“模式架构”:

    Mode.java

package beta01;


public interface Mode {
	
	public static final int F_12=123;
	public static final int NUM_1=97;
	public static final int NUM_2=98;
	
	public void start();
	public void doPress(int keyNum);
	public void doReleased(int keyNum);
	
	public void doLeftPressed(int x,int y);
	public void doLeftReleased(int x,int y);
	public void doRightPressed(int x,int y);
	public void doRightReleased(int x,int y);
	public void doMiddlePressed(int x,int y);
	public void doMiddleReleased(int x,int y);

}

 

 很简单吧,只要每一种子模式继承了它,就不会存在之前的逻辑问题了,呵呵。软件包括三种模式:菜单模式、录制模式及模拟模式。这里我全部都采用匿名类进行继承,后面会进行介绍。先来看看操作类:

      Operation.java

package module;

import java.awt.Point;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.math.BigDecimal;

public class Operation implements Externalizable {

	private static final long serialVersionUID = 1L;
	public static final int MODE_KEYBOARD=1;
	public static final int MODE_MOUSE=2;
	public static final int MODE_UNKNOWN=3;

	private Integer instruction = null; 	//键盘按键
	private Point point; 					//鼠标左击位置
	private Long delay;					//延迟时间

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		decode(in.readLine());
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeBytes(this.encode());
		out.writeChar('\n');
	}

	public Long getDelay() {
		return delay;
	}

	public void setDelay(Long delay) {
		this.delay = delay;
	}

	public Integer getInstruction() {
		return instruction;
	}

	public void setInstruction(Integer instruction) {
		this.instruction = instruction;
	}

	public Point getPoint() {
		return point;
	}

	public void setPoint(Point point) {
		this.point = point;
	}

	@Override
	public String toString() {
		return encode();
	}

	/**
	 * 将字符串解码成操作实体
	 * @param info
	 */
	public void decode(String info) {
		if (info != null && info.length() != 0) {
			String str[] = info.split("@");
			if (str.length == 2) {
				this.delay = (long)(round((Double.parseDouble(str[1].trim())*1000),0));
				String[] sstr = str[0].split(",");
				if (sstr.length == 2) {
					this.point = new Point(Integer.parseInt(sstr[0].trim()),
							Integer.parseInt(sstr[1].trim()));
				} else if (sstr.length == 1) {
					this.instruction = Integer.parseInt(sstr[0].trim());
				} else {
					this.delay = null;
				}
			}
		}
	}

	/**
	 * 对操作类进行编码成字符串,方便保存
	 * @return
	 */
	public String encode() {
		if (instruction == null) {
			return point.x + "," + point.y + "@" + round((double)delay/1000,1);
		} else {
			return instruction + "@" + round((double)delay/1000,1);
		}
	}
	
	public int getMode()
	{
		if(this.instruction==null&&this.point!=null)
		{
			return MODE_MOUSE;
		}
		else if(this.instruction!=null&&this.point==null)
		{
			return MODE_KEYBOARD;
		}
		return MODE_UNKNOWN;
	}

	/**
	 * 
	 * 提供精确的小数位四舍五入处理。
	 * 
	 * @param v 需要四舍五入的数字
	 * @param scale 小数点后保留几位
	 * @return 四舍五入后的结果
	 * 
	 */
	public static double round(double v, int scale) {
		if (scale < 0) {
			throw new IllegalArgumentException(
					"The scale must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(Double.toString(v));
		BigDecimal one = new BigDecimal("1");
		return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
	}

}

 

    主要负责记录每一次操作,包括鼠标及键盘操作,鼠标记录位置,键盘记录键码,两种都有一个延迟时间,注意延迟时间是相对时间,即本次操作的延迟时间是相对于上次操作而言,如第一个操作的延迟是1s,第二个操作的延迟是2.1s,那么第一个操作是在启动模拟模式后1秒后执行,第二个操作是在第一个操作执行完毕后的2.1秒过后执行。

InstructionTask.java:

package module;

import java.awt.AWTException;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.InputEvent;

import core.Task;

/**
 * 执行指令的任务
 * @author Micheal
 *
 */
public class InstructionTask implements Task {
	
	private static Robot robot;
	
	static {
		try {
			robot=new Robot();
		} catch (AWTException e) {
			e.printStackTrace();
		}
	}
	
	private Integer instruction;
	private Point point;
	
	public InstructionTask(){}
	
	public InstructionTask(Integer instruction)
	{
		this.instruction=instruction;
		this.point=null;
	}
	
	public InstructionTask(Point point)
	{
		this.point=point;
		this.instruction=null;
	}
	
	public Integer getInstruction() {
		return instruction;
	}

	public void setInstruction(Integer instruction) {
		this.instruction = instruction;
		this.point=null;
	}

	public Point getPoint() {
		return point;
	}

	public void setPoint(Point point) {
		this.point = point;
		this.instruction=null;
	}

	/**
	 * 执行任务
	 */
	public void perform() {
//		System.out.println("perform:"+toString());
		if(this.instruction!=null)
		{
			robot.keyPress(instruction);
			robot.keyRelease(instruction);
		}
		else if(this.point!=null)
		{
			robot.mouseMove(point.x,point.y);
			robot.mousePress(InputEvent.BUTTON1_MASK);
			robot.mouseRelease(InputEvent.BUTTON1_MASK);
		}
		else
		{
			System.out.println("!警告:非可执行操作!");
		}
		
	}
	
	@Override
	public String toString() {
		if(this.instruction!=null)
		{
			return String.valueOf(this.instruction);
		}
		else if(this.point!=null)
		{
			return this.point.toString();
		}
		else
		{
			return "nothing";
		}
	}

}

 

这里要说明下,由于我是准备支持多计时器的,所以才会用到任务列表及任务线程的问题。如果只是单单支持一个的话那么直接使用java.util.Timer定时器就可以了。

 

计时器Calculagraph.java:

package module;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import core.TaskThread;

/**
 * 计时器
 * @author Micheal
 * 线程执行,当系统时间达到操作延迟时间要求时,将操作指令传入执行器队列中
 *
 */
public class Calculagraph extends Thread implements Externalizable {
	
	private static final long serialVersionUID = 1L;

	private static TaskThread executive;
	
	private String title;
	private List<Operation> operations;
	private boolean runnable=true;
	private Timer timer;
	
	public Calculagraph(){}
	
	public Calculagraph(String title)
	{
		this.title=title;
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		this.title=in.readUTF();
		int count=in.readInt();
		in.readChar();
		operations=new LinkedList<Operation>();
		for(int i=0;i<count;i++)
		{
			Operation operation=new Operation();
			operation.readExternal(in);
			
			operations.add(operation);
		}
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeUTF(title);
		out.writeInt(operations.size());
		out.writeChar('\n');
		for(Operation op:operations)
		{
			op.writeExternal(out);
		}

	}
	
	@Override
	public synchronized void start() {
		super.start();
		executive=TaskThread.getInstance();
		executive.start();
	}
	
	@Override
	public void run() {
		Label:while(runnable)
		{
			if(timer==null)
			{
				timer=new Timer(false);		//创建非daemon线程
			}
			int count=1;
			for(Operation op:operations)
			{
				final InstructionTask task=new InstructionTask();
				switch(op.getMode())
				{
				case Operation.MODE_KEYBOARD:
					task.setInstruction(op.getInstruction());
					break;
				case Operation.MODE_MOUSE:
					task.setPoint(op.getPoint());
					break;
				default:
					continue;
				}
				timer.schedule(new TimerTask(){
					@Override
					public void run() {
						
						if(runnable==false)
						{
							synchronized(Calculagraph.this){
								Calculagraph.this.notify();
							}
							return;
						}
						
						executive.addTask(task);
						synchronized(Calculagraph.this){
							Calculagraph.this.notify();
						}
					}},op.getDelay());
				try {
					synchronized(this)
					{
						this.wait();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("执行操作:"+count+++"/"+operations.size()+"-->"+op);
				if(runnable==false) break Label;
			}
		}
		timer.cancel();
		executive.close();
		executive=null;
		System.out.println("--计时器已关闭--");
	}
	
	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public synchronized void close()
	{
		this.runnable=false;
		this.notify();
	}

	public List<Operation> getOperations() {
		return operations;
	}

	public void setOperations(List<Operation> operations) {
		this.operations = operations;
	}
	
	public void addOperation(Operation operation)
	{
		if(this.operations==null)
		{
			this.operations=new LinkedList<Operation>();
		}
		this.operations.add(operation);
	}

}

 

两大精华之一,实现对操作的模拟所在,使用了java多线程的wait,notify机制;

录制器TempRecorder.java:

package beta01;

import java.awt.Point;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import module.Operation;

public class TempRecorder {
	
	private static Long latestRecordTime;			//上次记录时间
	private static List<Operation> operations=new LinkedList<Operation>();
	private static Point pressPoint=new Point(-65525,-65525);
	private static Point releasePoint=new Point(-65525,-65525);
	
	public static void start(){
		/*清空数据*/
		latestRecordTime=new Date().getTime();
		operations=new LinkedList<Operation>();
	}
	
	public static void doPress(int keyNum){	
		Long currentTime=new Date().getTime();
		Long delay=currentTime-latestRecordTime;
		
		Operation operation=new Operation();
		operation.setInstruction(keyNum);
		operation.setDelay(delay);
		
		latestRecordTime=currentTime;
		operations.add(operation);
		
		System.out.println("记录操作:"+operation);
	}
	
	public static void doReleased(int keyNum) {}
	
	public static void doLeftPressed(int x, int y) {
		pressPoint.setLocation(x, y);
	}

	public static void doLeftReleased(int x, int y) {
		releasePoint.setLocation(x, y);
		
		/*
		 * 如果是click(press与release在同一个位置)则记录
		 */
		if(releasePoint.equals(pressPoint))
		{
			Long currentTime=new Date().getTime();
			Long delay=currentTime-latestRecordTime;
			
			Operation operation=new Operation();
			operation.setPoint(new Point(pressPoint.x,pressPoint.y));
			operation.setDelay(delay);
			
			latestRecordTime=currentTime;
			
			operations.add(operation);
			
			System.out.println("记录操作:"+operation);
		}
		
		//清空数据
		clearPoint(pressPoint);
		clearPoint(releasePoint);
	}
	
	private static void clearPoint(Point point)
	{
		point.setLocation(-65525,-65525);
	}

	public static List<Operation> getOperations() {
		return operations;
	}

}

 

细心的读者可能看到上面我的图中有个Recorder.java类,那个是暂时废弃的,暂时使用这一个。

Config.java:

package beta02;

import java.io.File;
import java.io.IOException;

import org.apache.xmlbeans.XmlException;
import org.dom4j.DocumentException;

import core.XMLFormater;

import script.CalculagraphDocument;

import module.Calculagraph;
import module.Operation;

public class Config {

	private static final String FILE_PATH = "script.xml";

	/**
	 * 导入脚本
	 * @param calculagraph
	 */
	public static void loadConfig(Calculagraph calculagraph) {
		File file = new File(FILE_PATH);
		try {
			CalculagraphDocument doc = CalculagraphDocument.Factory.parse(file);
			calculagraph.setTitle(doc.getCalculagraph().getTitle());
			int operationSize = doc.getCalculagraph().getOperations()
					.sizeOfOperationArray();
			for (int i = 0; i < operationSize; i++) {
				Operation operation = new Operation();
				operation.decode(doc.getCalculagraph().getOperations()
						.getOperationArray(i));
				calculagraph.addOperation(operation);
			}
		} catch (XmlException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 生成脚本
	 * @param calculagraph
	 */
	public static void saveConfig(Calculagraph calculagraph) {
		File file = new File(FILE_PATH);
		try {
			file.createNewFile();

			CalculagraphDocument doc = CalculagraphDocument.Factory
					.newInstance();
			if (calculagraph != null) {
				doc.addNewCalculagraph().setTitle(calculagraph.getTitle());
				if (calculagraph.getOperations() != null) {
					doc.getCalculagraph().addNewOperations();
					for (Operation op : calculagraph.getOperations()) {
						doc.getCalculagraph().getOperations().addOperation(
								op.encode());
					}
				}
			}
			doc.save(file);
			XMLFormater.format(file);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (DocumentException e) {
			e.printStackTrace();
		}
	}

}

 

为了支持XML文件保存与读取,我使用了XMLBean技术来实现,很方便的说,不了解的百度下。

XMLFormator.java:

package core;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

public class XMLFormater {

	public static void format(File file) throws DocumentException, IOException {
		
		SAXReader reader = new SAXReader();
	   	Document doc= (Document)reader.read(file);
		OutputFormat of = OutputFormat.createPrettyPrint();
        of.setIndent(true);
        of.setNewlines(true);
        of.setLineSeparator("\r\n");
        Writer fileWriter=new FileWriter(file);   
        XMLWriter xmlWriter=new XMLWriter(fileWriter,of);
        xmlWriter.write(doc);   
        xmlWriter.close();                      
	}

}

 实现对xml文件进行格式化。

Main.java:

package beta02;

import module.Calculagraph;

import org.sf.feeling.swt.win32.extension.hook.Hook;

import beta01.Mode;
import beta01.TempRecorder;

import core.KeyboardHookEventListener;
import core.MouseHookEventListener;

public class Main {
	
	private static Mode menuMode;
	private static Mode recordMode;
	private static Mode imitateMode;
	
	private static Mode currentMode;
	private static Calculagraph calculagraph;
	
	private static MouseHookEventListener mouseListener=null;
	private static KeyboardHookEventListener keyboardListener=null;

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		keyboardListener=new KeyboardHookEventListener(){

			@Override
			public void doPress(int keyNum) {
				if(currentMode!=null)
				{
					currentMode.doPress(keyNum);
				}
			}

			@Override
			public void doReleased(int keyNum) {
				if(currentMode!=null)
				{
					currentMode.doReleased(keyNum);
				}
			}
		};
		
		mouseListener=new MouseHookEventListener(){

			@Override
			protected void doLeftPressed(int x, int y) {
				if(currentMode!=null)
				{
					currentMode.doLeftPressed(x, y);
				}
				
			}

			@Override
			protected void doLeftReleased(int x, int y) {
				if(currentMode!=null)
				{
					currentMode.doLeftReleased(x, y);
				}
			}

			@Override
			protected void doMiddlePressed(int x, int y) {
				if(currentMode!=null)
				{
					currentMode.doMiddlePressed(x, y);
				}
			}

			@Override
			protected void doMiddleReleased(int x, int y) {
				if(currentMode!=null)
				{
					currentMode.doMiddleReleased(x, y);
				}
			}

			@Override
			protected void doRightPressed(int x, int y) {
				if(currentMode!=null)
				{
					currentMode.doRightPressed(x, y);
				}
			}

			@Override
			protected void doRightReleased(int x, int y) {
				if(currentMode!=null)
				{
					currentMode.doRightReleased(x, y);
				}
			}
			
		};
		
		menuMode=new Mode(){

			public void start() {
				System.out.println("请选择模式:");
				System.out.println("NUM1:记录模式");
				System.out.println("NUM2:模拟模式");
				System.out.println("F12:退出系统");
			}

			public void doPress(int keyNum) {
				switch(keyNum)
				{
				case Mode.NUM_1:
					currentMode=recordMode;
					recordMode.start();
					break;
				case Mode.NUM_2:
					currentMode=imitateMode;
					imitateMode.start();
					break;
				case Mode.F_12:
					System.out.println("--退出系统--");
					System.exit(0);
					break;
				default:
				}
			}

			public void doReleased(int keyNum) {}
			public void doLeftPressed(int x, int y) {}
			public void doLeftReleased(int x, int y) {}
			public void doMiddlePressed(int x, int y) {}
			public void doMiddleReleased(int x, int y) {}
			public void doRightPressed(int x, int y) {}
			public void doRightReleased(int x, int y) {}
			
		};
		
		recordMode=new Mode(){

			public void doPress(int keyNum) {
				if(keyNum==Mode.F_12)
				{
					
					calculagraph=new Calculagraph("Imitator");
					calculagraph.setOperations(TempRecorder.getOperations());
					Config.saveConfig(calculagraph);
					calculagraph=null;
					System.out.println("--记录模式已关闭--");
					
					currentMode=menuMode;
					menuMode.start();
					return;
				}
				TempRecorder.doPress(keyNum);
			}

			public void doReleased(int keyNum) {
				TempRecorder.doReleased(keyNum);
			}

			public void start() {
				TempRecorder.start();
				System.out.println("记录模式已启动,系统正在记录您的操作...");
			}
			
			public void doLeftPressed(int x, int y) {
				TempRecorder.doLeftPressed(x, y);
			}
			public void doLeftReleased(int x, int y) {
				TempRecorder.doLeftReleased(x, y);
			}
			public void doMiddlePressed(int x, int y) {}
			public void doMiddleReleased(int x, int y) {}
			public void doRightPressed(int x, int y) {}
			public void doRightReleased(int x, int y) {}
		};
		
		imitateMode=new Mode(){

			public void doPress(int keyNum) {
				if(keyNum==Mode.F_12)
				{
					calculagraph.close();
					calculagraph=null;
					System.out.println("--模拟模式已关闭--");
					currentMode=menuMode;
					menuMode.start();
				}
			}

			public void doReleased(int keyNum) {}

			public void start() {
				if(calculagraph==null)
				{
					calculagraph=new Calculagraph("Imitator");
				}
				
				Config.loadConfig(calculagraph);
				calculagraph.start();
				System.out.println("模拟模式已启动,系统正在模拟您的操作...");
			}
			
			public void doLeftPressed(int x, int y) {}
			public void doLeftReleased(int x, int y) {}
			public void doMiddlePressed(int x, int y) {}
			public void doMiddleReleased(int x, int y) {}
			public void doRightPressed(int x, int y) {}
			public void doRightReleased(int x, int y) {}
		};
		
		Hook.MOUSE.addListener(mouseListener);
		Hook.KEYBOARD.addListener(keyboardListener);
		
		Hook.MOUSE.install();
		Hook.KEYBOARD.install();
		
		currentMode=menuMode;
		menuMode.start();
			
	}

}

 

使用设计模式中的模板模式,负责程序调度。

 

最后生成的脚本文件如下:

<?xml version="1.0" encoding="UTF-8"?>

<calculagraph>
  <title>Imitator</title>
  <operations>
    <operation>710,918@2.0</operation>//@后面是延迟时间
    <operation>975,888@0.4</operation>
    <operation>841,920@0.4</operation>
    <operation>68@1.1</operation>
    <operation>71@0.3</operation>
    <operation>861,915@0.5</operation>
  </operations>
</calculagraph>

 

 

 附件是我打包好的程序,欢迎使用,:)

4
0
分享到:
评论
4 楼 pu02203 2012-03-04  
我把Confidant.jar, 丢进去eclipse, 里面怎么找不到任何java檔
都是org.apache.xmlbeans的东西
3 楼 micheal19840929 2009-09-17  
fengque531 写道
啊。。。。。LZ你花了多长时间做这个东西?

呵呵,也用不了多少时间,主要是其中的一些小模块在以前的开发过程中已经编写出来了,这里只是直接套用一下而已
2 楼 fengque531 2009-09-17  
啊。。。。。LZ你花了多长时间做这个东西?
1 楼 songlipeng 2009-09-17  
牛人。。java也能做外挂。。。

相关推荐

Global site tag (gtag.js) - Google Analytics