目录
一、实现思路
二、实现过程
1. 创建Java类
2. 实现读取数据方法
3. 实现修改数据方法
3.1. 修改关卡数
? ? ? ? ?3.2. 修改金币数
3.3. 解锁所有模式
3.4?解决输入错误
4. 实现写入数据方法
?三、实验测试
?四、完整代码
上次任务已经使用Hex Editor Neo工具成功修改游戏存档,具体实现探索和步骤请参考:修改植物大战僵尸游戏存档——跳关并快速实现财富自由,本次任务目的将使用java代码替换工具,实现同样的修改功能。
一、实现思路
- 读取数据:修改的user1.dat文件的本地路径已知,使用IO流知识读取数据并存放在程序中。
- 修改数据:利用控制台输入,获取用户修改意向,并根据任务的不同采取不同的修改策略。
- 写入数据:将修改后的数据重新写入到原文件中。
二、实现过程
1. 创建Java类
按图示创建Java工程。
?之后在创建好的Java工程src目录下创建Java类。
输入类名称就可以进行敲代码啦。如下图,定义两个全局变量,分别是可变数组userIndex,用来存放.dat文件下读取出来的数据、.dat所在的本地路径。并设为static类型,可以不用创建类对象就能使用。
2. 实现读取数据方法
有两种读取文件方式,读取文本形式使用Reader,读取二进制数据使用InputStream。FileInputStream是InputStream的一个子类。顾名思义,FileInputStream就是从文件流中读取数据。BufferedInputStream继承于FilterInputStream,提供缓冲输入流功能。缓冲输入流相对于普通输入流的优势是,它提供了一个缓冲数组,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户。由于从缓冲区里读取数据远比直接从物理数据源读取速度快。代码如下:
// 从二进制文件中读取数据(读取到的数据从十六进制数据自动转成十进制)
public static void readData(){
try
{
FileInputStream fis= new FileInputStream(fileName);
BufferedInputStream br = new BufferedInputStream(fis);
int record = -1;
while((record = br.read()) != -1)
{
userIndex.add(record); //将读到的数据添加到可变数组中(全局变量)
}
System.out.println("从文档中读取第一行数据显示(十进制):");
for(int i=0; i<16; i++){
System.out.print(userIndex.get(i) + " ");
}
System.out.println();
}
catch(Exception e)
{
System.out.print(e.toString());
}
}
?3. 实现修改数据方法
修改数据思路是:主方法中先用Scanner获取用户输入的任务类型,将用户输入数据以参数方式传给changeData方法,在方法内部判断,根据类型采取不同的修改方式。具体措施如下:
3.1. 修改关卡数
3.1.1 用Scanner获取用户输入的关卡数,得到类型为字符串,例"5-1"。
3.1.2 计算修改的十进制数(因为从文件中读取时十六进制自动转换成十进制):先用substring分别获取“-”前面和后面的数字,用变量存取(此时为字符串类型)。用valueof()方法将字符串转为数字,并用前数字*10加后数字。
3.1.3 将1.2的结果替换到表示关卡数的位置:用Hex Editor Neo表示第一行04位置(下标为04)。
public static void changeData(String type){
if (type.equals("1")){
System.out.println("请输入您要改的关卡:");
Scanner input=new Scanner(System.in);
String level=input.nextLine();
int firstLevel=Integer.valueOf(level.substring(0,1));
int lastLevel=Integer.valueOf(level.substring(2,3));
//因为从1-1开始的,5-1只需加41关即可
int addLevel=(firstLevel-1)*10+lastLevel;
//修改数据:关卡数在第一行04位置,即数组中下标为4
userIndex.set(4,addLevel);
}
?3.2. 修改金币数
?3.2.1?用Scanner获取用户输入的关卡数,得到类型为字符串,例"100000000"。
?3.2.2 计算:因为植物大战僵尸的内部机制,二进制表示是真实金币数的十分之一,因此需要将2.1结果转换为数字并除以10。文件中以十六进制存储,将除以10后的十进制数据转换成十六进制。例如"100,000,000/10d=989680h"。
?3.2.3 存储:在.dat文件中,金币数量是由08,09,0a,0b是存储位,包含8个十六进制位,因此需要将不够位数的在前面补齐。此思路用for循环,循环次数是(8-十六进制数的length)。例如"989680->00989680"。补齐之后以两位为单位,分别按低位->高位的顺序替换08->0b的值。例如"80,96,98,00"分别替换"08,09,0a,0b"位的值。代码如下:
else if (type.equals("2")){
System.out.println("请输入您要改的金币数量:");
Scanner input=new Scanner(System.in);
String money=input.nextLine();
//需要加到的金钱
int needMoney=Integer.valueOf(money)/10;
String HexMoney=Integer.toHexString(needMoney);
//在十六进制数前面补0到8位
String zero=new String();
for (int i=0;i<8-HexMoney.length();i++){
zero+="0";
}
HexMoney=zero+HexMoney;//补全的十六进制
//最后两位,对应第一行08位置,进行替换
int data08=Integer.valueOf(HexMoney.substring(HexMoney.length()-2));
userIndex.set(8, Integer.parseInt(String.valueOf(data08),16));
//倒数第3,4位,对应第一行09位置,进行替换
int data09=Integer.valueOf(HexMoney.substring(4,6));
userIndex.set(9, Integer.parseInt(String.valueOf(data09),16));
//正数第3,4位,对应第一行0a位置,进行替换
int data0a=Integer.valueOf(HexMoney.substring(2,4));
userIndex.set(10, Integer.parseInt(String.valueOf(data0a),16));
//正数第1,2位,对应第一行0a位置,进行替换
int data0b=Integer.valueOf(HexMoney.substring(0,2));
userIndex.set(11, Integer.parseInt(String.valueOf(data0b),16));
}
3.3. 解锁所有模式
按照上次的实验,发现只需将0c位的数改为01即可达到目的。代码如下:
else if(type.equals("3")){
//修改数据:关卡数在第一行0c位置,即数组中下标为12
userIndex.set(12,1);
}
3.4?解决输入错误
当输入非1-3,则提示并重新获取信息,利用递归重新进行判断。
else{
// 输入有误,重新接收数字,用递归方法修改数据
System.out.println("输入有误,请输入1-3:");
Scanner input=new Scanner(System.in);
String level=input.nextLine();
changeData(level);
return;
}
4. 实现写入数据方法
?利用FileOutStream写入数据,并且为完全替换,append设为false。
// 将数组以二进制写入文件中
public static void writeData() throws IOException {
FileOutputStream fileWriter=new FileOutputStream(fileName,false);
for (int singleData:userIndex) {
fileWriter.write(singleData);
}
//写入文件
fileWriter.close();
}
?三、实验测试
?1. 任务一修改关卡数为5-1:
修改关卡数为6-1:
?
?2. 任务二修改金币数量为1024:
?修改金币数量为100,000,000:
3. 任务三解锁所有模式:
?4. 其他出错检查:
?四、完整代码
import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;
public class modifyPlantsVsZombies {
static ArrayList<Integer> userIndex=new ArrayList<>();
static String fileName="C:\\ProgramData\\PopCap Games\\PlantsVsZombies\\userdata\\user1.dat";
public static void main(String[] args) {
readData();
System.out.println("请输入任务标号(1/2/3):");
Scanner input=new Scanner(System.in);
String level=input.nextLine();
changeData(level);
}
// 从二进制文件中读取数据(在输出到控制台时十六进制数据自动转成十进制)
public static void readData(){
try
{
FileInputStream fis= new FileInputStream(fileName);
BufferedInputStream br = new BufferedInputStream(fis);
int record = -1;
while((record = br.read()) != -1)
{
userIndex.add(record);
}
System.out.println("从文档中读取第一行数据显示(十进制):");
for(int i=0; i<16; i++){
System.out.print(userIndex.get(i) + " ");
}
System.out.println();
}
catch(Exception e)
{
System.out.print(e.toString());
}
}
// 判断任务,并根据用户输入意向进行修改
public static void changeData(String type){
if (type.equals("1")){
System.out.println("请输入您要改的关卡:");
Scanner input=new Scanner(System.in);
String level=input.nextLine();
int firstLevel=Integer.valueOf(level.substring(0,1));
int lastLevel=Integer.valueOf(level.substring(2,3));
//因为从1-1开始的,5-1只需加41关即可
int addLevel=(firstLevel-1)*10+lastLevel;
//修改数据:关卡数在第一行04位置,即数组中下标为4
userIndex.set(4,addLevel);
}else if (type.equals("2")){
System.out.println("请输入您要改的金币数量:");
Scanner input=new Scanner(System.in);
String money=input.nextLine();
//需要加到的金钱
int needMoney=Integer.valueOf(money)/10;
String HexMoney=Integer.toHexString(needMoney);
//在十六进制数前面补0到8位
String zero=new String();
for (int i=0;i<8-HexMoney.length();i++){
zero+="0";
}
HexMoney=zero+HexMoney;//补全的十六进制
//最后两位,对应第一行08位置,进行替换
int data08=Integer.valueOf(HexMoney.substring(HexMoney.length()-2));
userIndex.set(8, Integer.parseInt(String.valueOf(data08),16));
//倒数第3,4位,对应第一行09位置,进行替换
int data09=Integer.valueOf(HexMoney.substring(4,6));
userIndex.set(9, Integer.parseInt(String.valueOf(data09),16));
//正数第3,4位,对应第一行0a位置,进行替换
int data0a=Integer.valueOf(HexMoney.substring(2,4));
userIndex.set(10, Integer.parseInt(String.valueOf(data0a),16));
//正数第1,2位,对应第一行0a位置,进行替换
int data0b=Integer.valueOf(HexMoney.substring(0,2));
userIndex.set(11, Integer.parseInt(String.valueOf(data0b),16));
}else if(type.equals("3")){
//修改数据:关卡数在第一行0c位置,即数组中下标为12
userIndex.set(12,1);
}else{
// 输入有误,重新接收数字,用递归方法修改数据
System.out.println("输入有误,请输入1-3:");
Scanner input=new Scanner(System.in);
String level=input.nextLine();
changeData(level);
return;
}
//修改之后输出第一行
System.out.println("修改后第一行数据显示(十进制):");
for(int i=0; i<16; i++){
System.out.print(userIndex.get(i) + " ");
}
//将数组以二进制写入文件中
try {
writeData();
} catch (IOException e) {
e.printStackTrace();
}
}
// 将数组以二进制写入文件中
public static void writeData() throws IOException {
FileOutputStream fileWriter=new FileOutputStream(fileName,false);
for (int singleData:userIndex) {
fileWriter.write(singleData);
}
//写入文件
fileWriter.close();
}
}
心得
上次的手动修改使我了解到游戏中二进制的储存机制,并接触到了曾经认为遥不可及的事情。这次换代码实现,计算效率一下子提高了好多,只要编译成功,下次不需要再进行手动计算,就可以达到"一次编程,随时运行"的效果。另外,实现的过程也是让人感到乐在其中,首先将整体思路写出来,按照每步想实现的功能去查资料并实现。从IO流的应用到二进制的处理,再到字符串与数字的相互转换,这些知识点都在我心中有了更深的印记。感谢CSDN给我这次实训的机会,让我对Java又多了些热爱。劳伦斯曾说过“成功的秘诀,是在养成迅速去做的习惯,要趁着潮水涨得最高的一刹那,不但没有阻力,而且能帮助你迅速地成功”,对语言的真正掌握也只有不断地探索和实践。加油吧,少年!
|