魔兽争霸3是一款非常著名的即时战略游戏。相信很多人都听过sky、moon、grubby这些名字,还有塔魔infi、中国的鬼王ted、刚猛的fly、飘逸的th000等选手。遗憾的是WCG2013是魔兽争霸3的最后一届,我自己也去现场观看了魔兽的总决赛。此外,还有DOTA、真三、澄海3C等著名的地图。

魔兽争霸的录像大家都知道,是用来回放的,文件后缀名是.w3g,保存在魔兽争霸下的REPLAY目录下。现在很多软件可以分析魔兽争霸录像,直接可以查看录像的玩家、地图,以及玩家的APM等信息。

最近在YY对战平台打魔兽,经常能遇到Java程序员,说明Java程序员中有很多魔兽争霸3的玩家,这里将Java解析魔兽争霸3录像的方法贡献给同是WAR3玩家的小伙伴们。

魔兽争霸3录像文件由一个头部(Header)多个压缩数据块(compressed data blocks)组成。本文主要内容是解析Header部分,压缩数据块部分的解析会在后续的博文中详细介绍。

Header结构:

Header部分包含了录像的最基本的信息,大小是固定的前68个字节,此后的全部是压缩数据块。对于1.06版本及之前的录像,Header部分大小是64字节,由于版本太古老这里就不考虑了。下面的代码中也不再支持老版本的录像。

Header中每个部分的意义:

1~28字节(28个字符):固定的字符串"Warcraft III recorded game\0x1A\0"。
29~32字节(4个字节):Header部分的总字节数,对于1.07版本及之后,是68(0x44),对于1.06版本及之前是64(0x40)。
33~36字节(4个字节):压缩数据块的压缩数据总字节数,即解压前。
37~40字节(4个字节):录像版本标识(0表示1.06版本及之前版本,1表示1.07版本及之后版本)。
41~44字节(4个字节):压缩数据块解压缩后的总字节数。
45~48字节(4个字节):压缩数据块的个数。
49~52字节(4个字节):一个字符串标识,"WAR3"表示非冰封王座,"W3XP"表示冰封王座。
53~56字节(4个字节):版本号(例如24即是1.24版本)。
57~58字节(2个字节):构建号(build number)。
59~60字节(2个字节):0x0000表示单人游戏,0x8000(十进制32768)表示多人游戏。
61~64字节(4个字节):录像时长(毫秒数),需要注意的是,这个时长不包括游戏中暂停的时长。
65~68字节(4个字节):Header部分CRC32校验码(包含这四个字节但是要都设为0)。

可以使用EditPlus的Hex Viewer方式打开w3g文件查看Header部分。

在这里可以发现一个问题,除了第一个字符串"Warcraft III recorded game\0x1A\0"以外,其他每个部分的字节顺序都是倒过来的。例如Header部分总字节数是0x44000000,"W3XP"字符串顺序是"PX3W"。这是因为这里使用的是小字节序(Little-Endian),也就是字节顺序和正常的顺序完全相反,所以在读取的时候应该将其倒过来读。

Java解析Header:

知道了Header部分的结构,下面就可以用Java语言来解析Header了。

首先定义一个Replay类,表示一场录像,构造函数传入录像文件File。为了方便,将文件转换成字节数组,再将字节数组传给Header类进行处理。

Replay.java

package com.xxg.w3gparser;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;public class Replay {private Header header;public Replay(File w3gFile) throws IOException, W3GException {byte[] fileBytes = fileToByteArray(w3gFile);header = new Header(fileBytes);}/*** 将文件转换成字节数组* @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;}}

在Header类中,按小字节序读取所有的Header信息。Header的最后四个字节是CRC32循环冗余检验码,Java中可以使用java.util.zip.CRC32类来计算,下面的代码中校验了计算结果和Header中是否一致。有关CRC32的介绍可以查看相关资料,这里不再介绍。

Header.java

package com.xxg.w3gparser;import java.util.zip.CRC32;public class Header {public static final String BEGIN_TITLE = "Warcraft III recorded game\u001A\0";private long headerSize;private long compressedDataSize;private long headerVersion;private long uncompressedDataSize;private long compressedDataBlockCount;private String versionIdentifier;private long versionNumber;private int buildNumber;private int flag;private long duration;public Header(byte[] fileBytes) throws W3GException {// 读取开头的字符串"Warcraft III recorded game\u001A\0"String beginTitle = new String(fileBytes, 0, 28);System.out.println("1-28字节:" + beginTitle);if (!BEGIN_TITLE.equals(beginTitle)) {throw new W3GException("录像格式不正确。");}// header部分总大小(版本小于或等于V1.06是0x40(64),版本大于或等于V1.07是0x44(68))headerSize = LittleEndianTool.getUnsignedInt32(fileBytes, 28);System.out.println("29-32字节:" + headerSize);if (headerSize != 0x44) {throw new W3GException("不支持V1.06及以下版本的录像。");}// 压缩文件大小compressedDataSize = LittleEndianTool.getUnsignedInt32(fileBytes, 32);System.out.println("33-36字节:" + compressedDataSize);// header版本(版本小于或等于V1.06是0,版本大于或等于V1.07是1)headerVersion = LittleEndianTool.getUnsignedInt32(fileBytes, 36);System.out.println("37-40字节:" + headerVersion);if (headerVersion != 1) {throw new W3GException("不支持V1.06及以下版本的录像。");}// 解压缩数据大小uncompressedDataSize = LittleEndianTool.getUnsignedInt32(fileBytes, 40);System.out.println("41-44字节:" + uncompressedDataSize);// 压缩数据块数量compressedDataBlockCount = LittleEndianTool.getUnsignedInt32(fileBytes, 44);System.out.println("45-48字节:" + compressedDataBlockCount);// WAR3:非冰封王座录像,W3XP冰封王座录像versionIdentifier = LittleEndianTool.getString(fileBytes, 48, 4);System.out.println("49-52字节:" + versionIdentifier);// 版本号(例如1.24版本对应的值是24)versionNumber = LittleEndianTool.getUnsignedInt32(fileBytes, 52);System.out.println("53-56字节:" + versionNumber);// Build号buildNumber = LittleEndianTool.getUnsignedInt16(fileBytes, 56);System.out.println("57-58字节:" + buildNumber);// 单人游戏(0x0000) 多人游戏(0x8000,对应十进制32768)flag = LittleEndianTool.getUnsignedInt16(fileBytes, 58);System.out.println("59-60字节:" + flag);// 录像时长(毫秒)duration = LittleEndianTool.getUnsignedInt32(fileBytes, 60);System.out.println("61-64字节:" + duration);// CRC32校验码long crc32 = LittleEndianTool.getUnsignedInt32(fileBytes, 64);System.out.println("65-68字节:" + crc32);// 这里来校验CRC32,将最后四位也就是CRC32所在的四个字节设为0后计算CRC32的值CRC32 crc32Tool = new CRC32();crc32Tool.update(fileBytes, 0, 64);crc32Tool.update(0);crc32Tool.update(0);crc32Tool.update(0);crc32Tool.update(0);System.out.println("计算CRC32:" + crc32Tool.getValue());// 判断Header中后四位读取的CRC32的值和计算得到的值比较,看是否一致if (crc32 != crc32Tool.getValue()) {throw new W3GException("Header部分CRC32校验不通过。");}}public long getHeaderSize() {return headerSize;}public long getCompressedDataSize() {return compressedDataSize;}public long getHeaderVersion() {return headerVersion;}public long getUncompressedDataSize() {return uncompressedDataSize;}public long getCompressedDataBlockCount() {return compressedDataBlockCount;}public String getVersionIdentifier() {return versionIdentifier;}public long getVersionNumber() {return versionNumber;}public int getBuildNumber() {return buildNumber;}public int getFlag() {return flag;}public long getDuration() {return duration;}}

Header中用到了LittleEndianTool是用来按小字节序读取数据的工具类。

LittleEndianTool.java

package com.xxg.w3gparser;/*** Little-Endian(小字节序)工具类* @author 叉叉哥(806223819@qq.com)*/
public class LittleEndianTool {/*** 以Little-Endian(小字节序)方式读取字节数组中的一个16位(2个字节)无符号整数* @param bytes 字节数组* @param offset 开始字节的位置索引* @return 16位(2个字节)无符号整数*/public static int getUnsignedInt16(byte[] bytes, int offset) {int b0 = bytes[offset] & 0xFF;int b1 = bytes[offset + 1] & 0xFF;return b0 + (b1 << 8);}/*** 以Little-Endian(小字节序)方式读取字节数组中的一个32位(4个字节)无符号整数* @param bytes 字节数组* @param offset 开始字节的位置索引* @return 32位(4个字节)无符号整数*/public static long getUnsignedInt32(byte[] bytes, int offset) {long b0 = bytes[offset] & 0xFFl;long b1 = bytes[offset + 1] & 0xFFl;long b2 = bytes[offset + 2] & 0xFFl;long b3 = bytes[offset + 3] & 0xFFl;return b0 + (b1 << 8) + (b2 << 16) + (b3 << 24);}/*** 以Little-Endian(小字节序)方式读取字节数组中的字符串* @param bytes 字节数组* @param offset 开始字节的位置索引* @param length 需要读取的长度* @return 读取的字符串*/public static String getString(byte[] bytes, int offset, int length) {byte[] temp = new byte[length];for(int i = 0; i < length; i++) {temp[i] = bytes[offset + length - i - 1];}return new String(temp);}}

这里需要注意的是,Java中int类型4个字节大小,但是由于是有符号的整数,补码的最高位是符号位,所以对于Header中的4个字节的无符号整数,必须要用long类型才足够。2个字节的无符号整数需要使用Java中的int而不能是short。

另外,Header中用到了W3GException异常。

W3GException.java

package com.xxg.w3gparser;public class W3GException extends Exception {public W3GException(String message) {super(message);}}

最后用main方法调用这些代码来测试。

Test.java

package com.xxg.w3gparser;import java.io.File;
import java.io.IOException;public class Test {public static void main(String[] args) throws IOException, W3GException {Replay replay = new Replay(new File("E:/魔兽争霸3冰封王座/REPLAY/100729_[NE]EHOME.ReMinD_VS_[ORC]WemadeFOX_Lyn_EchoIsles_RN.w3g"));Header header = replay.getHeader();System.out.println("WAR3录像基本信息为:");System.out.println("版本:1." + header.getVersionNumber() + "." + header.getBuildNumber());long duration = header.getDuration();long second = (duration / 1000) % 60;long minite = (duration / 1000) / 60;if (second < 10) {System.out.println("时长:" + minite + ":0" + second);} else {System.out.println("时长:" + minite + ":" + second);}}}

输出结果:

1-28字节:Warcraft III recorded game  
29-32字节:68
33-36字节:125736
37-40字节:1
41-44字节:311296
45-48字节:38
49-52字节:W3XP
53-56字节:24
57-58字节:6059
59-60字节:32768
61-64字节:783600
65-68字节:1414752232
计算CRC32:1414752232
WAR3录像基本信息为:
版本:1.24.6059
时长:13:03

参考文档:http://w3g.deepnode.de/files/w3g_format.txt

作者:叉叉哥   转载请注明出处:http://blog.csdn.net/xiao__gui/article/details/17882303

Java解析魔兽争霸3录像W3G文件(一):Header相关推荐

  1. Java解析魔兽争霸3录像W3G文件(五):Action和APM计算

    在游戏进行中,玩家会进行各种操作,例如编队.移动.技能.造建筑等,这些操作就是Action.APM(Actions Per Minute),表示每分钟的操作次数,APM可以很好的反映玩家的手速和实力, ...

  2. Java解析魔兽争霸3录像W3G文件(三):解析游戏开始前的信息

    上一篇博文中,通过对压缩数据块的解压缩以及合并,得到了解压缩的字节数组.从现在开始,就要处理这个数据. 这个部分的数据主要包括两大类信息:一类是游戏开始前的信息,例如游戏地图,游戏玩家,队伍.种族情况 ...

  3. java 解析m3u8的实例_m3u8文件完整实例及TS流抓取

    参考文档: http://blog.csdn.net/blueboyhi/article/details/40107683 如下 是一个华数影视的M3U8文件链接 http://chyd-sn.was ...

  4. 用Java解析:您可以使用的所有工具和库

    如果需要从Java解析语言或文档,则从根本上讲有三种方法可以解决问题: 使用支持该特定语言的现有库:例如用于解析XML的库 手动构建自己的自定义解析器 生成解析器的工具或库:例如ANTLR,可用于构建 ...

  5. java读取war3模型_GitHub - wucao/jw3gparser: Java Warcraft Ⅲ Replay Parser(Java解析《魔兽争霸3》游戏录像工具)...

    jw3gparser Java解析<魔兽争霸3>游戏录像工具,可解析w3g.nwg(网易对战平台录像)格式录像. 使用方法 public class Test { public stati ...

  6. IDEA Java解析GeoJson.json文件

    IDEA Java解析GeoJson.json文件 一.遇到的问题 1. 无法导入成功 2. org.geotools.StyleFactory is not an ImageIO SPI class ...

  7. java 解析 csv 文件

    文章分类:JavaEye 一.貌似有bug,不行用 二.或 三. 的方法 Java代码   import java.io.BufferedReader; import java.io.FileInpu ...

  8. 用正则表达式和java解析csv文件

    用正则表达式和java解析csv文件 作者:弹着钢琴设计  来源:博客园  发布时间:2009-06-15 18:31  阅读:337 次  原文链接   [收藏]   在解析csv文件之前,先来看看 ...

  9. Jvm之用java解析class文件

    前言: 身为一个java程序员,怎么能不了解JVM呢,倘若想学习JVM,那就又必须要了解Class文件,Class之于虚拟机,就如鱼之于水,虚拟机因为Class而有了生命.<深入理解java虚拟 ...

  10. Java解析JSON文件

    Java解析基于json-simple库,github地址. https://github.com/fangyidong/json-simple 解析是把字符串变为json文件,或者把json文件变成 ...

最新文章

  1. 2018蓝桥杯省赛---java---B---1(第几天)
  2. cocos2d 走动椭圆
  3. mysql授予权限和撤销权限的关系_MySQL数据库常用的授予权限和撤销权限的命令讲解...
  4. java锁机制ppt_总结:Java锁机制
  5. Singleton 与 MonoState 模式
  6. 修改云服务器上文件夹只读属性,求助:为什么我电脑里的文件夹会自动变为只读属性?...
  7. windows环境下面安装neo4j出错记录
  8. SQLyog 注册码(包含企业版注册码)
  9. Macbook pro/air 2013 late -2014 使用转接卡更换NVME SSD休眠不醒问题的解决办法
  10. Pytorch之反向传播
  11. CBR,VBR,ABR,CQP四种编码方式。
  12. dw整个网站html文件怎么放一起,Dreamweaver模板如何批量做网页
  13. MATLAB之GIF动图的绘制
  14. 穷举法(枚举法)实例解析
  15. 小米miui开发版系统获取root权限的方法
  16. 读书笔记《敏捷项目管理》第七章 定义产品愿景和产品路线图
  17. Hydration failed because the initial UI does not match what was rendered on the server.问题原因之一
  18. sh shell实现自动杀死cpu占用最高的pid,同时当cpu超过95%自动重启apache
  19. android 电池电量显示不正常,vivo电量显示不正常怎么解决?vivo手机电量校准教程...
  20. 面试题-list集合删除操作,可能会出现什么问题?

热门文章

  1. 航空三字代码表_国际国内城市三字代码查询-城市航空代码表
  2. oracle11g rman实例,oracle11g rman备份与恢复详细实例
  3. Matlab画图和点标记
  4. C/C++项目源码——数字雨DigitalRain
  5. 大学计算机—计算思维导论 中国大学mooc 哈尔滨工业大学 测验题目和答案
  6. Python小练习——电影数据集TMDB预处理
  7. 清华姚班和100个张小龙
  8. 清华姚班和100个“张小龙” | 中国AI天才养成计划
  9. Matlab 四阶龙格库塔法求解二元常微分方程组
  10. Py之Scipy:Scipy库(高级科学计算库)的简介、安装、使用方法之详细攻略