3286人阅读
Java语言(52)
魔兽争霸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部分总字节数是0x,&W3XP&字符串顺序是&PX3W&。这是因为这里使用的是小字节序(Little-Endian),也就是字节顺序和正常的顺序完全相反,所以在读取的时候应该将其倒过来读。
Java解析Header:
知道了Header部分的结构,下面就可以用Java语言来解析Header了。
首先定义一个Replay类,表示一场录像,构造函数传入录像文件File。为了方便,将文件转换成字节数组,再将字节数组传给Header类进行处理。
Replay.java
package com.xxg.w3
import java.io.ByteArrayOutputS
import java.io.F
import java.io.FileInputS
import java.io.IOE
public class Replay {
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];
while((n = fileInputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, n);
} finally {
fileInputStream.close();
return byteArrayOutputStream.toByteArray();
public Header getHeader() {
在Header类中,按小字节序读取所有的Header信息。Header的最后四个字节是CRC32循环冗余检验码,Java中可以使用java.util.zip.CRC32类来计算,下面的代码中校验了计算结果和Header中是否一致。有关CRC32的介绍可以查看相关资料,这里不再介绍。
Header.java
package com.xxg.w3
import java.util.zip.CRC32;
public class Header {
public static final String BEGIN_TITLE = &Warcraft III recorded game\u001A\0&;
private long headerS
private long compressedDataS
private long headerV
private long uncompressedDataS
private long compressedDataBlockC
private String versionI
private long versionN
private int buildN
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 headerS
public long getCompressedDataSize() {
return compressedDataS
public long getHeaderVersion() {
return headerV
public long getUncompressedDataSize() {
return uncompressedDataS
public long getCompressedDataBlockCount() {
return compressedDataBlockC
public String getVersionIdentifier() {
return versionI
public long getVersionNumber() {
return versionN
public int getBuildNumber() {
return buildN
public int getFlag() {
public long getDuration() {
Header中用到了LittleEndianTool是用来按小字节序读取数据的工具类。
LittleEndianTool.java
package com.xxg.w3
* Little-Endian(小字节序)工具类
* @author 叉叉哥()
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 & 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.w3
public class W3GException extends Exception {
public W3GException(String message) {
super(message);
最后用main方法调用这些代码来测试。
package com.xxg.w3
import java.io.F
import java.io.IOE
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);
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字节:
计算CRC32:
WAR3录像基本信息为:
版本:1.24.6059
时长:13:03
参考文档:
作者:叉叉哥 & 转载请注明出处:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:430664次
积分:5410
积分:5410
排名:第3778名
原创:84篇
评论:596条
文章:10篇
阅读:33930
(4)(2)(2)(1)(1)(3)(1)(1)(1)(1)(2)(2)(1)(2)(3)(7)(3)(1)(2)(3)(2)(4)(2)(1)(5)(2)(1)(2)(4)(2)(1)(2)(3)(1)(7)(1)(2)先回答你第一个问题吧,正版的5.45图,是公认的比赛图。这张图里,飓风一个英雄带是绝对可以连续吹的,净化会有几秒的CD,所以你不用担心换英雄吹牛。他净化再快,你一直狂点TC,他也T不出来!神助玩家就另当别论了。但是5,49的飓风是有CD的,你想连续吹只有换英雄了。- -、&第二个换传的问题,我要说的是,并不是你换的快,CD就没了。换传的问题,我也一直很纠结,问啥有时候能换成功,有时候没用呢?这个只有编辑这张地图的大哥才能够解释了。我给你一个我个人的方法吧,虽然不算好,但是成功几率还是有80%.90%的。就是传送的CD读了4分之1之后,最好是过一点,CD还剩下4分之2多一点的时候换传送。你玩黑暗抓人,千万不要紧张,看好在传,以免闪传送。祝你游戏愉快!&希望能够帮到你!
追问:恩、我也一直这样倒传的、灰常感谢、补充:
没事呵呵,都是玩澄海的
已有*** (2)
吹只要你速度快,谁吹都行阿
追问:不是啊、手速是够。但是如果我刚吹起来他就净化、我也准备好了再吹、可是有时候是飓风CD时间还没结束、或者刚快结束!反正就是CD还存在一瞬间我操作不就失效了吗?再操作时候就没CD时间了,就是CD时间很短!如果对方手速慢还可以、快点的话直接就被T了啊!补充:自己得练习加快手速,, 实在不行多英雄切换吹他追问:现在不是不不知道这俩方法哪个练法比较正确吗!
我想楼主的问题是:怎么才能把澄海3C玩好,对吗?
我觉得应该是这样:
① 先认识3C,包括3C的打法,英雄,技巧等等。 ② 之后是多多练习,多多益善,纸上谈兵是没有用的,这点我有经验。③当你摆脱了菜鸟这个级别后,就要进阶中级玩家的水平,要多和高手过招,多看高手得视频。
我玩CH~3C也有快两年了,我就是按照上面的步骤去做的,现在技术也还不赖。
我再教你一点3C的小技巧吧,很实用的:
1、AM的水元素10级,战斗中只需升9点留1点.。因为10级水元素价值106澄海币,9级就是53澄海币。少升一点,给对方的钱就少一半。
2、胤真的录象告诉我们:小黑赚钱要把怪捆在对方塔内,要控制好石象鬼辅助赚钱,要随时A掉跟自己抢钱的那两只车车。0E5~0s3D(R
3、多人对战的时候,杀***可以增加300元的收入,黑暗方具备杀龙的条件,开局记得引龙增加收入。(针对目前引下龙的习惯极有可能会被光明破坏,新手段是引上龙,学会引上龙也是必须滴)
4、知道黑暗会引龙,光明KOG当仁不让要比黑侠更快,把龙引走,断掉黑暗300元的财路。
5、对战时间英雄站位是一门艺术,英雄集中在一起一旦被抓,遇到高手一般无翻身的机会。所以,分开站位是必定、必须和必要以及必不可少。
6、最经典的KOG带上闪烁(8000大元),在关键配合作战的时候,闪烁进树林,开宁静。另外在上路作战,闪烁到30洞上面的牌子后面开宁静。很难被发现,尤其混乱作战的时候。)
7、偷英雄系列(因为偷英雄不是光彩的事,也是不公平对战,早该限制掉了。故省略一千字)
8、强悍的打40洞方法,黑暗三英雄后出传,牛到30级冲撞进40洞,小黑召唤4只石像鬼,借牛做传送点,然后让 4只鸟去打,英雄飞出继续在外征战。[注,色狼永远是千年老色狼,只要喊一声,MM要进来了,他就只看洞门,是不会看天的
9、防御小强、BM记得随时***监控器(眼睛),监视他们的一切行动,疯狂打击压制他们顶风作案。
10、强悍不死牛头要典,身上必备装备:传送、闪烁、沉默、飓风(关键时候吹起对方传送英雄,另外也可以把自己吹起救命,也可以救队友)。
11、强悍阴险的隐型坦克:适用对象(GA、牛头、MK等群晕英雄)。买件黑夜隐型袍子,穿上,站中间,按H(默念:看我不到、看我不到、看我不到)。抓住机会群晕,队友瞬间传送配合展开杀戮。
12.WD偷15洞,开局拿药卖药后买1个全知,(543新图无挑战玩家13的BUG,所以全知这300块收不回来.)剩下买复活,在自己基地买3组复活,然后再下路赚点钱买1血棒、1飓风,主升闪烁和刀阵。10级的时候召唤仇恨女神在外赚钱,然后WD在黑暗离15洞最近的地方闪烁进去,插血棒在上台,闪烁下去杀(注意别吸引外面守卫着的怪),拿了2个骷髅就出去。
13、拿了骷髅的WD,3路赚钱,抓住机会中路杀黑侠(合理利用飓风),记住不要放多,放2、3组骷髅就够了,这时她的黑箭还不能一次射掉你的骷髅,成功几率很大。除非他一直龟缩在家里。
14、中间刷4条魔免龙的时候,一定要不吝啬放大招,都是钱,第2个好处:升级低级英雄。(另还会吸引上下路的龙龙)。
15、2V2中,首发GA+KOG,KOG开局拿5个药,卖了买个飞艇。走下路赚钱,买多一两组复活。等10级GA开始走中路,KOG上飞艇背后包抄黑侠,捆+树人+GA联合摧残,基本黑侠必死。杀完小黑继续下路,15级抢到15洞的话,利用飞艇,上下路赚钱。很快就可以出2奶。
16、传送权仗的使用:同一个英雄传送一次以后要等15秒才能传送第二次。这时可以把传送交换到另一个英雄身上,这样就可以马上再次使用传送了,不用在浪费时间等15秒。
17、逃跑技巧:骑士(PK时看情况及时开无敌)带传送,BM带传送隐身站开,英雄海危机时,及时传送逃跑。还有一种利用飓风吹起传送英雄,及时逃跑。适当带净化权杖,关键英雄被晕可以解。
18、SM的应用不只是+蓝用,SM有个净化是很好用的,可以除去自己身上的负面魔法 除去敌人的正面魔法,还可以驱除--飓风,;很重要的一点是它用在敌人身上可以让他瞬间不能动。例:给小Y变了可以净化,给鹿缠了可以净化.
19、快捷键不只是用来放技能的,加技能点可以用快捷键;到商店买东西也可以用快捷键 例:重生A 、沉默E 、红棒W 、萨满S 、蓝棒B 、敏捷书A 、力量书S 、智力书I ,熟悉后能省不少事,加快速度!
20、牛的30冲撞后会自动的踩一脚晕,假如人家是来骗招,你冲撞的时候人家传送跑了,有什么办法让他不踩呢?很简单,在冲的时候就控制一下,按S停止就OK了。这样敌人再飞来杀,你的晕的CD就不用等了,直接送他一记晕!
21、小小强操作的一些技巧:招出来后用CTRL点,可以一次全选;另外用右键点要移动去的地方,然后SHIFT+B 就可以减少操作,到点后他自己埋入地下。
22.团灭对方后可以利用15洞,插个眼睛在下路商店附近,关键英雄藏匿于15里面(比如47之类),抓住时机从15洞出来一个冰,再次团灭对手。
23、清清楚楚赚钱,明明白白练兵!
24、AM首发克制黑侠赚钱,买飓风权杖,10级黑侠放10打龙的时候,看己方龙的血差不多一半的时候,召唤水元素飞过去吹起黑侠,控制水元素攻击己方的龙龙,不让黑侠赚到龙的钱。拖延黑侠出2奶时间,可以无阻碍拿到15。
25、黑侠MM开局拿药卖钱后不买SM,直接买了个飞艇和棒子。走中路,看到AM带SM出现在上路,上飞艇过去直接变了AM的SM(而且带一个复活的),黑MM+SM上飞艇回中路赚钱。不过这招很少用,常常因为血不好耗而死给对方的兵个人感觉这个用来对付GA首发不错,当GA点金用了之后,上飞艇把GA的SM变了,他只能干瞪眼.
26、AM(大法师)打15的时候,可以在右上角靠监狱的地方召唤水元素,然后水元素会有若干在监狱里,再飞进去杀,可以省不少时间。大法放30存在施放时间,如果想取消魔法结界的施放,按S就可以马上取消。
27、小Y加点,要看作战情况。如果2V2独自走上或下路有毒蛇守卫就加,其次加治疗,妖术加一点即可,还是留下9点。这样19级就有10级蛇了。赚钱就没问题了。1V1的时候也一样道理,不过治疗与蛇就没主次之分了,往往看录象光明3英雄的时候杀黑暗,Y的作用很大,无敌被破,召一堆蛇出来是很恐怖的,加上GA和Y双加混合使用,保证己方血量充足。 【澄海
28、经常看到有人玩51放30的时候就放出1个冰圈就没了,51的30冰刺出的多少是看你释放的距离,所以请点远一点的地方。放得好的冰刺可以秒杀一群英雄。
29、黑暗1V1买了TC以后如果不拿15,尽量准备ES和TC一人一组风杖,光明出Y有无敌后一般会用骷髅来围杀你,这时候风杖的存在则是扭转局势的关键,利用风吹时无敌的条件,捆住Y破掉无敌射一箭吹自己一次,将骷髅全部射死并转化为己方的骷髅,TC的晕和冲击波、KOG的捆,往往能反杀光明。
30、黑暗团灭对方后,立即回去买飞艇运送牛头(最好穿上黑夜隐身袍子)到中路树林里,一旦光明中路买复活,立即沉默外加冲撞(往往光明注意的是前面,没注意树林里的乾坤。如果采用47效果会更好),其他配合传送杀戮。
略懂社热议