Java解析魔兽争霸3录像W3G文件(一):Header
魔兽争霸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相关推荐
- Java解析魔兽争霸3录像W3G文件(五):Action和APM计算
在游戏进行中,玩家会进行各种操作,例如编队.移动.技能.造建筑等,这些操作就是Action.APM(Actions Per Minute),表示每分钟的操作次数,APM可以很好的反映玩家的手速和实力, ...
- Java解析魔兽争霸3录像W3G文件(三):解析游戏开始前的信息
上一篇博文中,通过对压缩数据块的解压缩以及合并,得到了解压缩的字节数组.从现在开始,就要处理这个数据. 这个部分的数据主要包括两大类信息:一类是游戏开始前的信息,例如游戏地图,游戏玩家,队伍.种族情况 ...
- java 解析m3u8的实例_m3u8文件完整实例及TS流抓取
参考文档: http://blog.csdn.net/blueboyhi/article/details/40107683 如下 是一个华数影视的M3U8文件链接 http://chyd-sn.was ...
- 用Java解析:您可以使用的所有工具和库
如果需要从Java解析语言或文档,则从根本上讲有三种方法可以解决问题: 使用支持该特定语言的现有库:例如用于解析XML的库 手动构建自己的自定义解析器 生成解析器的工具或库:例如ANTLR,可用于构建 ...
- java读取war3模型_GitHub - wucao/jw3gparser: Java Warcraft Ⅲ Replay Parser(Java解析《魔兽争霸3》游戏录像工具)...
jw3gparser Java解析<魔兽争霸3>游戏录像工具,可解析w3g.nwg(网易对战平台录像)格式录像. 使用方法 public class Test { public stati ...
- IDEA Java解析GeoJson.json文件
IDEA Java解析GeoJson.json文件 一.遇到的问题 1. 无法导入成功 2. org.geotools.StyleFactory is not an ImageIO SPI class ...
- java 解析 csv 文件
文章分类:JavaEye 一.貌似有bug,不行用 二.或 三. 的方法 Java代码 import java.io.BufferedReader; import java.io.FileInpu ...
- 用正则表达式和java解析csv文件
用正则表达式和java解析csv文件 作者:弹着钢琴设计 来源:博客园 发布时间:2009-06-15 18:31 阅读:337 次 原文链接 [收藏] 在解析csv文件之前,先来看看 ...
- Jvm之用java解析class文件
前言: 身为一个java程序员,怎么能不了解JVM呢,倘若想学习JVM,那就又必须要了解Class文件,Class之于虚拟机,就如鱼之于水,虚拟机因为Class而有了生命.<深入理解java虚拟 ...
- Java解析JSON文件
Java解析基于json-simple库,github地址. https://github.com/fangyidong/json-simple 解析是把字符串变为json文件,或者把json文件变成 ...
最新文章
- 2018蓝桥杯省赛---java---B---1(第几天)
- cocos2d 走动椭圆
- mysql授予权限和撤销权限的关系_MySQL数据库常用的授予权限和撤销权限的命令讲解...
- java锁机制ppt_总结:Java锁机制
- Singleton 与 MonoState 模式
- 修改云服务器上文件夹只读属性,求助:为什么我电脑里的文件夹会自动变为只读属性?...
- windows环境下面安装neo4j出错记录
- SQLyog 注册码(包含企业版注册码)
- Macbook pro/air 2013 late -2014 使用转接卡更换NVME SSD休眠不醒问题的解决办法
- Pytorch之反向传播
- CBR,VBR,ABR,CQP四种编码方式。
- dw整个网站html文件怎么放一起,Dreamweaver模板如何批量做网页
- MATLAB之GIF动图的绘制
- 穷举法(枚举法)实例解析
- 小米miui开发版系统获取root权限的方法
- 读书笔记《敏捷项目管理》第七章 定义产品愿景和产品路线图
- Hydration failed because the initial UI does not match what was rendered on the server.问题原因之一
- sh shell实现自动杀死cpu占用最高的pid,同时当cpu超过95%自动重启apache
- android 电池电量显示不正常,vivo电量显示不正常怎么解决?vivo手机电量校准教程...
- 面试题-list集合删除操作,可能会出现什么问题?
热门文章
- 航空三字代码表_国际国内城市三字代码查询-城市航空代码表
- oracle11g rman实例,oracle11g rman备份与恢复详细实例
- Matlab画图和点标记
- C/C++项目源码——数字雨DigitalRain
- 大学计算机—计算思维导论 中国大学mooc 哈尔滨工业大学 测验题目和答案
- Python小练习——电影数据集TMDB预处理
- 清华姚班和100个张小龙
- 清华姚班和100个“张小龙” | 中国AI天才养成计划
- Matlab 四阶龙格库塔法求解二元常微分方程组
- Py之Scipy:Scipy库(高级科学计算库)的简介、安装、使用方法之详细攻略