Java解析魔兽争霸3录像W3G文件(四):解析游戏进行时的信息(一)

2014-11-24 03:16:45 · 作者: · 浏览: 0

在上一篇博文中,通过解析压缩数据块解压缩后的数据的前一部分,可以获取到游戏开始前的一些信息,紧接着游戏开始前的信息之后,就是游戏进行时的信息了,其中包括玩家游戏中的操作,例如造建筑,出兵,攻击,移动等,还包括玩家游戏中的聊天信息,玩家退出游戏等。

游戏进行时的信息由很多个数据块组成。这些数据块有几种类型,每个数据块的第一个字节就是数据块ID,用于标识数据块的类型。

下面列出各种数据块类型及其对应的数据块ID、数据块字节数和数据块结构:

1、0x17 - 玩家离开游戏的数据块
数据块ID:0x17
数据块字节数:14字节
结构:
1字节:数据块ID,0x17;
2~5字节:原因;
6字节:玩家ID;
7~10字节:结果;
11~14字节:未知。

2、0x20 - 玩家聊天信息的数据块
数据块ID:0x20
数据块字节数:n + 4字节
结构:
1字节:数据块ID,0x20;
2字节:玩家ID;
3~4字节:数据块剩余的数据的字节数n;
5字节:flag;
6~9字节:聊天模式,0x00:对所有玩家,0x01:对队友,0x02:对裁判或观看者,0x03或大于0x03:对指定玩家,玩家的slotNumber是该值减去3的结果,注意这里是slotNumber而不是玩家ID;
10~n+4字节:聊天内容,字符串,最后一个字节是0x00。

3、0x1E/0x1F 游戏时间段(TimeSlot)数据块
数据块ID:0x1E或0x1F
数据块字节数:n+3字节
结构:
1字节:数据块ID,0x1E或0x1F;
2~3字节:数据块剩余的数据的字节数n,最小值可能是2;
4~5字节:时间段的时间长度(毫秒,值一般是100毫秒左右);
6~n+3字节:玩家在这个时间段内的操作信息,当n为2时这部分不存在。这部分内容在下面一篇博文中再进行解析。

以上三种类型的数据块是需要解析的数据块,下面还有几种类型的数据块就不用去解析:

4、0x1A/0x1B/0x1C,5字节;
5、0x22,6字节;
6、0x23,11字节;
7、0x2F,9字节。

其中,游戏时间段(TimeSlot)数据块是游戏时间进度的标识,每个时间段100毫秒左右,其中包含玩家在这段时间内的操作信息。而游戏时间段数据块中的毫秒数累加后,就是游戏进行到的时间。比如玩家的操作、聊天、离开游戏的时间,就可以用其对应数据块之前的所有TimeSlot数据块中的时间累加来计算。

Java解析游戏进行时的信息:

添加ReplayData.java用来解析游戏进行时的信息。

ReplayData.java

package com.xxg.w3gparser;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

public class ReplayData {
	
	/**
	 * 解压缩的字节数组
	 */
	private byte[] uncompressedDataBytes;
	
	/**
	 * 解析的字节位置
	 */
	private int offset;
	
	/**
	 * 玩家列表
	 */
	private List
  
    playerList;
	
	/**
	 * 游戏进行时的时间(毫秒)
	 */
	private long time;
	
	/**
	 * 聊天信息集合
	 */
	private List
   
     chatList = new ArrayList
    
     (); public ReplayData(byte[] uncompressedDataBytes, int offset, List
     
       playerList) throws W3GException, UnsupportedEncodingException { this.uncompressedDataBytes = uncompressedDataBytes; this.offset = offset; this.playerList = playerList; analysis(); } /** * 解析 */ private void analysis() throws UnsupportedEncodingException, W3GException { byte blockId = 0; while ((blockId = uncompressedDataBytes[offset]) != 0) { switch (blockId) { // 聊天信息 case 0x20: analysisChatMessage(); break; // 时间段(一般是100毫秒左右一段) case 0x1E: case 0x1F: analysisTimeSlot(); break; // 玩家离开游戏 case 0x17: analysisLeaveGame(); break; // 未知的BlockId case 0x1A: case 0x1B: case 0x1C: offset += 5; break; case 0x22: offset += 6; break; case 0x23: offset += 11; break; case 0x2F: offset += 9; break; // 无效的Block default: throw new W3GException("无效Block,ID:" + blockId); } } } /** * 解析聊天信息 */ private void analysisChatMessage() throws UnsupportedEncodingException { ChatMessage chatMessage = new ChatMessage(); offset++; byte playerId = uncompressedDataBytes[offset]; chatMessage.setFrom(getPlayById(playerId)); offset++; int bytes = LittleEndianTool.getUnsignedInt16(uncompressedDataBytes, offset); offset += 2; offset++; long mode = LittleEndianTool.getUnsignedInt32(uncompressedDataBytes, offset); if(mode >= 3) { int receiverPlayerId = (int) (mode - 3); chatMessage.setTo(getPlayBySlotNumber(receiverPlayerId)); } chatMessage.setMode(mode); offset += 4; String message = new String(uncompressedDataBytes, offset, bytes - 6, "UTF-8"); chatMessage.setMessage(message); offset += bytes - 5; chatMessage.setTime(time); chatList.add(chatMessage); } /** * 解析一个时间块 */ private void analysisTimeSlot() { offset++; int bytes = LittleEndianTool.getUnsignedInt16(uncompressedDataBytes, offset); offs