目錄
一、實作思路
二、實作程序
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又多了些熱愛,勞倫斯曾說過“成功的秘訣,是在養成迅速去做的習慣,要趁著潮水漲得最高的一剎那,不但沒有阻力,而且能幫助你迅速地成功”,對語言的真正掌握也只有不斷地探索和實踐,加油吧,少年!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/340564.html
標籤:其他
