java解析魔兽争霸3录像_Java解析魔兽争霸3录像W3G文件(三):解析游戏开始前的信息...
上一篇博文中,通過對壓縮數據塊的解壓縮以及合并,得到了解壓縮的字節數組。從現在開始,就要處理這個數據。
這個部分的數據主要包括兩大類信息:一類是游戲開始前的信息,例如游戲地圖,游戲玩家,隊伍、種族情況,高級選項等等,這些信息都是在進入游戲之前已經確定的東西;另一類是游戲進行時的信息,這塊包括玩家游戲過程中的操作、游戲中的聊天等。其中,游戲開始前的信息占解壓縮后的數據的前一小部分,緊接著后面的一大部分保存著游戲進行時的信息。
本文介紹如何解析游戲開始前的信息。
游戲開始前的信息的結構:
注:在下面各部分結構解釋中,灰色字體標注的信息不對其進行解析,就不再詳細介紹,要想了解可以參考w3g_format.txt文檔。
一、總體結構
1、4 字節:未知。2、variable字節:主機玩家記錄(詳細查看【二、玩家記錄】)。
3、variable字節:游戲名稱,字符串,以0x00結束。
4、1字節:空字節,0x00。5、variable字節:特殊編碼的數據(包括游戲設置、地圖、創建者),以0x00結束(詳細查看【三、特殊編碼的數據】)。
6、4字節:玩家數量。
7、4字節:游戲類型。
8、4字節:未知。
9、variable字節:加入游戲的玩家列表(詳細查看:【四、加入游戲的玩家列表】以及【二、玩家記錄】)。
10、variable字節:Slot列表(詳細查看:【五、Slot列表】)。
二、玩家記錄
1、1字節:玩家類型,0x00主機,0x16加入游戲的玩家(【四、加入游戲的玩家列表】)。
2、1字節:玩家ID。
3、variable字節:玩家名稱,以0x00結束。
4、1字節:附加數據大小,0x01或0x08。
5、1或8字節:附加數據。
三、特殊編碼的數據
這是一段特殊編碼的數據,該部分需要解碼后才能繼續解析,解碼的方式直接看下面的代碼,這里不再介紹。
解碼后:
1、4字節:游戲設置,這部分包含一些高級選項,如下圖,不過這部分很少有人去改變,所以這里不再去解析了。
2、5字節:未知。
3、4字節:地圖校驗。
4、variable字節:地圖路徑,字符串,以0x00結束。
5、variable字節:創建者,字符串,以0x00結束。
四、加入游戲的玩家列表
如果有多個玩家加入游戲,每個玩家對應一個下面的結構。由于是加入游戲的玩家,所以每個玩家對應的數據都是0x16開頭。當遍歷到第一個字節不是0x16時玩家列表就結束了。注意,加入游戲的玩家列表中不包含電腦玩家,電腦玩家在【五、Slot列表】中。
1、variable字節:玩家記錄(詳細查看【二、玩家記錄】)。
2、4字節:0x00000000。
五、Slot列表
一個Slot是指游戲開始前的界面的一個玩家位置。如下圖,即是4個Slot。
1、1字節:固定0x19。
2、2字節:下面的數據的字節數。
3、1字節:Slot數量。
4、variable字節:Slot記錄的列表,其中包含多個Slot記錄,數量即上面一個字節的值(詳細查看【六、Slot記錄】)。
5、4字節:隨機種子。
6、1字節:隊伍、種族是否可選擇。
7、1字節:地圖中的位置數量。
六、Slot記錄
每個Slot占9個字節:
1、1字節:對應的玩家ID,電腦玩家是0x00。
2、1字節:地圖下載百分比(一般都是100)。
3、1字節:Slot狀態,0x00空的,0x01關閉著的,0x02使用中的。
4、1字節:是否是電腦玩家,0x00非電腦玩家,0x01電腦玩家。
5、1字節:隊伍,0~11分別表示隊伍1到隊伍12,12表示裁判或觀看者。
6、1字節:顏色,0紅1藍2青3紫4黃5橘黃6綠7粉8灰9淺藍10深綠11棕12裁判或觀看者
7、1字節:種族,0x01/0x41人族,0x02/0x42獸族,0x04/0x44暗夜精靈,0x08/0x48不死族,0x20/0x60隨機。
8、1字節:電腦難度,0x00簡單的,0x01中等難度的,0x02令人發狂的。
9、1字節:障礙(也就是血量百分比),0x32,0x3C,0x46,0x50,0x5A,0x64之一,分別表示50%到100%。
Java解析:
創建一個UncompressedData類,用于處理解壓縮后的數據。
UncompressedData.java
package com.xxg.w3gparser;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
public class UncompressedData {
/**
* 解壓縮的字節數組
*/
private byte[] uncompressedDataBytes;
/**
* 解析的字節位置
*/
private int offset;
/**
* 玩家列表
*/
private List playerList = new ArrayList();
/**
* 游戲名稱
*/
private String gameName;
/**
* 地圖路徑
*/
private String map;
/**
* 游戲創建者名稱
*/
private String createrName;
public UncompressedData(byte[] uncompressedDataBytes) throws UnsupportedEncodingException, W3GException {
this.uncompressedDataBytes = uncompressedDataBytes;
// 跳過前4個未知字節
offset += 4;
// 解析第一個玩家
analysisPlayerRecode();
// 游戲名稱(UTF-8編碼)
int begin = offset;
while(uncompressedDataBytes[offset] != 0) {
offset++;
}
gameName = new String(uncompressedDataBytes, begin, offset - begin, "UTF-8");
offset++;
// 跳過一個空字節
offset++;
// 解析一段特殊編碼的字節串,其中包含游戲設置、地圖和創建者
analysisEncodedBytes();
// 跳過PlayerCount、GameType、LanguageID
offset += 12;
// 解析玩家列表
while(uncompressedDataBytes[offset] == 0x16) {
analysisPlayerRecode();
// 跳過4個未知的字節0x00000000
offset += 4;
}
// GameStartRecord - RecordID、number of data bytes following
offset += 3;
// 解析每個Slot
byte slotCount = uncompressedDataBytes[offset];
offset++;
for(int i = 0; i < slotCount; i++) {
analysisSlotRecode(i);
}
// RandomSeed、RandomSeed、StartSpotCount
offset += 6;
}
/**
* 解析PlayerRecode
* @throws UnsupportedEncodingException
*/
private void analysisPlayerRecode() throws UnsupportedEncodingException {
Player player = new Player();
playerList.add(player);
// 是否是主機(0為主機)
byte isHostByte = uncompressedDataBytes[offset];
boolean isHost = isHostByte == 0;
player.setHost(isHost);
offset++;
// 玩家ID
byte playerId = uncompressedDataBytes[offset];
player.setPlayerId(playerId);
offset++;
// 玩家名稱(UTF-8編碼)
int begin = offset;
while(uncompressedDataBytes[offset] != 0) {
offset++;
}
String playerName = new String(uncompressedDataBytes, begin, offset - begin, "UTF-8");
player.setPlayerName(playerName);
offset++;
// 附加數據大小
int additionalDataSize = uncompressedDataBytes[offset];
offset++;
// 加上附加數據大小
offset += additionalDataSize;
}
/**
* 解析特殊編碼的字節串
* @throws UnsupportedEncodingException
*/
private void analysisEncodedBytes() throws UnsupportedEncodingException {
int begin = offset;
while(uncompressedDataBytes[offset] != 0) {
offset++;
}
// 編碼的數據和解碼后的數據的長度
int encodeLength = offset - begin - 1;
int decodeLength = encodeLength - (encodeLength - 1) / 8 - 1;
// 編碼的數據和解碼后的數據
byte[] encodeData = new byte[encodeLength];
byte[] decodeData = new byte[decodeLength];
// 將編碼字節串部分拷貝成一個單獨的字節數組,便于解析
System.arraycopy(uncompressedDataBytes, begin, encodeData, 0, encodeLength);
// 解碼(解碼的代碼來自于http://w3g.deepnode.de/files/w3g_format.txt文檔4.3部分,由C語言代碼翻譯成Java)
byte mask = 0;
int decodePos = 0;
int encodePos = 0;
while (encodePos < encodeLength) {
if (encodePos % 8 == 0) {
mask = encodeData[encodePos];
} else {
if ((mask & (0x1 << (encodePos % 8))) == 0) {
decodeData[decodePos++] = (byte) (encodeData[encodePos] - 1);
} else {
decodeData[decodePos++] = encodeData[encodePos];
}
}
encodePos++;
}
// 直接跳過游戲設置,這部分不再解析了
int decodeOffset = 13;
int decodeBegin = decodeOffset;
// 地圖路徑
while(decodeData[decodeOffset] != 0) {
decodeOffset++;
}
map = new String(decodeData, decodeBegin, decodeOffset - decodeBegin, "UTF-8");
decodeOffset++;
// 主機(游戲創建者)玩家名稱
decodeBegin = decodeOffset;
while(decodeData[decodeOffset] != 0) {
decodeOffset++;
}
createrName = new String(decodeData, decodeBegin, decodeOffset - decodeBegin, "UTF-8");
decodeOffset++;
offset++;
}
/**
* 解析每個Slot
*/
private void analysisSlotRecode(int slotNumber) {
// 玩家ID
byte playerId = uncompressedDataBytes[offset];
offset++;
// 跳過地圖下載百分比
offset++;
// 狀態 0空的 1關閉的 2使用的
byte slotStatus = uncompressedDataBytes[offset];
offset++;
// 是否是電腦
byte computerPlayFlag = uncompressedDataBytes[offset];
boolean isComputer = computerPlayFlag == 1;
offset++;
// 隊伍
byte team = uncompressedDataBytes[offset];
offset++;
// 顏色
byte color = uncompressedDataBytes[offset];
offset++;
// 種族
byte race = uncompressedDataBytes[offset];
offset++;
// 電腦難度
byte aiStrength = uncompressedDataBytes[offset];
offset++;
// 障礙(血量百分比)
byte handicap = uncompressedDataBytes[offset];
offset++;
// 設置玩家列表
if(slotStatus == 2) {
Player player= null;
if(!isComputer) {
player = getPlayById(playerId);
} else {
player = new Player();
playerList.add(player);
}
player.setComputer(isComputer);
player.setAiStrength(aiStrength);
player.setColor(color);
player.setHandicap(handicap);
player.setRace(race);
player.setTeamNumber(team);
player.setSlotNumber(slotNumber);
}
}
/**
* 通過玩家ID獲取Player對象
* @param playerId 玩家ID
* @return 對應的Player對象
*/
private Player getPlayById(byte playerId) {
Player p = null;
for(Player player : playerList) {
if(playerId == player.getPlayerId()) {
p = player;
break;
}
}
return p;
}
public List getPlayerList() {
return playerList;
}
public String getGameName() {
return gameName;
}
public String getMap() {
return map;
}
public String getCreaterName() {
return createrName;
}
}
Player類表示每個玩家的信息,包括電腦玩家。其中slotNumber表示玩家的Slot位置,從0開始,后面將會用于解析聊天信息。
Player.java
package com.xxg.w3gparser;
public class Player {
/**
* 是否是主機
*/
private boolean isHost;
/**
* 玩家ID
*/
private byte playerId;
/**
* 玩家的Slot位置
*/
private int slotNumber;
/**
* 玩家名稱
*/
private String playerName;
/**
* 是否是電腦
*/
private boolean isComputer;
/**
* 0~11:隊伍1~12
* 12:裁判或觀看者
*/
private byte teamNumber;
/**
* 玩家顏色,0紅1藍2青3紫4黃5橘黃6綠7粉8灰9淺藍10深綠11棕12裁判或觀看者
*/
private byte color;
/**
* 種族:0x01/0x41人族,0x02/0x42獸族,0x04/0x44暗夜精靈,0x08/0x48不死族,0x20/0x60隨機
*/
private byte race;
/**
* 電腦級別:0簡單的,1中等難度的,2令人發狂的
*/
private byte aiStrength;
/**
* 障礙,也就血量百分比,取值有50,60,70,80,90,100
*/
private byte handicap;
public boolean isHost() {
return isHost;
}
public void setHost(boolean isHost) {
this.isHost = isHost;
}
public byte getPlayerId() {
return playerId;
}
public void setPlayerId(byte playerId) {
this.playerId = playerId;
}
public String getPlayerName() {
return playerName;
}
public void setPlayerName(String playerName) {
this.playerName = playerName;
}
public boolean isComputer() {
return isComputer;
}
public void setComputer(boolean isComputer) {
this.isComputer = isComputer;
}
public byte getTeamNumber() {
return teamNumber;
}
public void setTeamNumber(byte teamNumber) {
this.teamNumber = teamNumber;
}
public byte getColor() {
return color;
}
public void setColor(byte color) {
this.color = color;
}
public byte getRace() {
return race;
}
public void setRace(byte race) {
this.race = race;
}
public byte getAiStrength() {
return aiStrength;
}
public void setAiStrength(byte aiStrength) {
this.aiStrength = aiStrength;
}
public byte getHandicap() {
return handicap;
}
public void setHandicap(byte handicap) {
this.handicap = handicap;
}
public int getSlotNumber() {
return slotNumber;
}
public void setSlotNumber(int slotNumber) {
this.slotNumber = slotNumber;
}
}
在Replay.java中,加入UncompressedData解析。
Replay.java
package com.xxg.w3gparser;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.DataFormatException;
public class Replay {
private Header header;
private UncompressedData uncompressedData;
public Replay(File w3gFile) throws IOException, W3GException, DataFormatException {
// 將文件轉為字節數組,方便處理
byte[] fileBytes = fileToByteArray(w3gFile);
// 解析Header
header = new Header(fileBytes);
// 遍歷解析每個壓縮數據塊,解壓縮,合并
long compressedDataBlockCount = header.getCompressedDataBlockCount();
byte[] uncompressedDataBytes = new byte[0]; // 所有壓縮數據塊中數據解壓合并到這個數組中
int offset = 68;
for(int i = 0; i < compressedDataBlockCount; i++) {
CompressedDataBlock compressedDataBlock = new CompressedDataBlock(fileBytes, offset);
// 數組合并
byte[] blockUncompressedData = compressedDataBlock.getUncompressedDataBytes();
byte[] temp = new byte[uncompressedDataBytes.length + blockUncompressedData.length];
System.arraycopy(uncompressedDataBytes, 0, temp, 0, uncompressedDataBytes.length);
System.arraycopy(blockUncompressedData, 0, temp, uncompressedDataBytes.length, blockUncompressedData.length);
uncompressedDataBytes = temp;
int blockCompressedDataSize = compressedDataBlock.getCompressedDataSize();
offset += 8 + blockCompressedDataSize;
}
// 處理解壓縮后的字節數組
uncompressedData = new UncompressedData(uncompressedDataBytes);
}
/**
* 將文件轉換成字節數組
* @param w3gFile 文件
* @return 字節數組
* @throws IOException
*/
private byte[] fileToByteArray(File w3gFile) throws IOException {
FileInputStream fileInputStream = new FileInputStream(w3gFile);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int n;
try {
while((n = fileInputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, n);
}
} finally {
fileInputStream.close();
}
return byteArrayOutputStream.toByteArray();
}
public Header getHeader() {
return header;
}
public UncompressedData getUncompressedData() {
return uncompressedData;
}
}
修改main方法,測試以上代碼。
Test.java
package com.xxg.w3gparser;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.zip.DataFormatException;
public class Test {
public static void main(String[] args) throws IOException, W3GException, DataFormatException {
Replay replay = new Replay(new File("C:/Documents and Settings/Administrator/桌面/131230_[UD]962030958_VS_[ORC]flygogogo_AncientIsles_RN.w3g"));
Header header = replay.getHeader();
System.out.println("版本:1." + header.getVersionNumber() + "." + header.getBuildNumber());
long duration = header.getDuration();
System.out.println("時長:" + convertMillisecondToString(duration));
UncompressedData uncompressedData = replay.getUncompressedData();
System.out.println("游戲名稱:" + uncompressedData.getGameName());
System.out.println("游戲創建者:" + uncompressedData.getCreaterName());
System.out.println("游戲地圖:" + uncompressedData.getMap());
List list = uncompressedData.getPlayerList();
for(Player player : list) {
System.out.println("---玩家" + player.getPlayerId() + "---");
System.out.println("玩家名稱:" + player.getPlayerName());
if(player.isHost()) {
System.out.println("是否主機:主機");
} else {
System.out.println("是否主機:否");
}
if(player.getTeamNumber() != 12) {
System.out.println("玩家隊伍:" + (player.getTeamNumber() + 1));
switch(player.getRace()) {
case 0x01:
case 0x41:
System.out.println("玩家種族:人族");
break;
case 0x02:
case 0x42:
System.out.println("玩家種族:獸族");
break;
case 0x04:
case 0x44:
System.out.println("玩家種族:暗夜精靈");
break;
case 0x08:
case 0x48:
System.out.println("玩家種族:不死族");
break;
case 0x20:
case 0x60:
System.out.println("玩家種族:隨機");
break;
}
switch(player.getColor()) {
case 0:
System.out.println("玩家顏色:紅");
break;
case 1:
System.out.println("玩家顏色:藍");
break;
case 2:
System.out.println("玩家顏色:青");
break;
case 3:
System.out.println("玩家顏色:紫");
break;
case 4:
System.out.println("玩家顏色:黃");
break;
case 5:
System.out.println("玩家顏色:橘");
break;
case 6:
System.out.println("玩家顏色:綠");
break;
case 7:
System.out.println("玩家顏色:粉");
break;
case 8:
System.out.println("玩家顏色:灰");
break;
case 9:
System.out.println("玩家顏色:淺藍");
break;
case 10:
System.out.println("玩家顏色:深綠");
break;
case 11:
System.out.println("玩家顏色:棕");
break;
}
System.out.println("障礙(血量):" + player.getHandicap() + "%");
if(player.isComputer()) {
System.out.println("是否電腦玩家:電腦玩家");
switch (player.getAiStrength()) {
case 0:
System.out.println("電腦難度:簡單的");
break;
case 1:
System.out.println("電腦難度:中等難度的");
break;
case 2:
System.out.println("電腦難度:令人發狂的");
break;
}
} else {
System.out.println("是否電腦玩家:否");
}
} else {
System.out.println("玩家隊伍:裁判或觀看者");
}
}
}
private static String convertMillisecondToString(long millisecond) {
long second = (millisecond / 1000) % 60;
long minite = (millisecond / 1000) / 60;
if (second < 10) {
return minite + ":0" + second;
} else {
return minite + ":" + second;
}
}
}
程序輸出:
版本:1.26.6059
時長:15:39
游戲名稱:當地局域網內的游戲 (96
游戲創建者:962030958
游戲地圖:Maps\E-WCLMAP\(2)AncientIsles.w3x
---玩家1---
玩家名稱:962030958
是否主機:主機
SlotNumber:0
玩家隊伍:1
玩家種族:不死族
玩家顏色:黃
障礙(血量):100%
是否電腦玩家:否
---玩家2---
玩家名稱:flygogogo
是否主機:否
SlotNumber:1
玩家隊伍:2
玩家種族:獸族
玩家顏色:藍
障礙(血量):100%
是否電腦玩家:否
總結
以上是生活随笔為你收集整理的java解析魔兽争霸3录像_Java解析魔兽争霸3录像W3G文件(三):解析游戏开始前的信息...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Pug/jade快速上手教程
- 下一篇: pycharm导出依赖包_使用pycha