Valhalla Legends Forums Archive | Java Programming | Reading a Warcraft III Replay???

AuthorMessageTime
Miraglade
How would you go about gathing information from a warcraft replay file?
--Players, Map, Races, etc.

I have looked at this site http://photoduhavre.free.fr/notes.htm , but im not sure how to get to that stage :/

Any information about how to go about this would be helpful (a rough outline would be nice)
*Any code would need to be in Java of course
*If you can tell already im pretty much a noob with Java (pretty much all programming)
--So ignor my noobness or any misspellings :)
July 10, 2004, 9:15 AM
Tuberload
I found some more current information on the replay file format here.

This does sound interesting so I might try and take on this project. If I do I will post my results when I get them.
July 10, 2004, 6:02 PM
Tuberload
Here is what I have so far. It reads the first part of the header and displays it. Tomorrow I will make it parse the sub header and data blocks. From there all you have to do is figure out the compression, and how to play the replay. HTH.

WC3RHeader.java:
[code]package tuberload.wc3.replay;
import java.io.BufferedInputStream;
import java.io.IOException;
import tuberload.util.Array;
import tuberload.util.ByteBuffer;
/**
*   Constructs the Warcraft III Replay header information.
*/
public class WC3RHeader
{
   // Replay header information
   private char[] file_desc = new char[26];
   private int unknown_1;            // always 0x1A00 so fatr
   private int head_size;
   private int file_size;
   private int replay_ver;
   private int decomp_size;
   private int data_blocks;         // number of data blockes
   // Subheader information
   private WC3RSubHeader sub_head;
   
   /**
    *   Initialize the header.
    */
   public WC3RHeader (BufferedInputStream bos) throws IOException
   {
      // create byte buffer
      ByteBuffer bb = new ByteBuffer();
      byte[] data = new byte[bos.available()];
      bos.read (data, 0, bos.available());
      bb.insertByte (data);
      // get file description
      data = new byte[26];
      data = bb.getBytes (0, 26);
      for (int i = 0; i < 26; i++)
         file_desc[i] = (char)data[i];
      // unknown 1
      unknown_1 = bb.getWord (0x1a);
      // header size/subheader location
      head_size = bb.getDword (0x1c);
      // file size
      file_size = bb.getDword (0x20);
      // replay version
      replay_ver = bb.getDword (0x24);
      // decompressed file size
      decomp_size = bb.getDword (0x28);
      // the number of compressed data blocks
      data_blocks = bb.getDword (0x2c);
      // get the subheader information
      if (replay_ver == 0x00)
      {
         // load sub header
      }
      else if (replay_ver == 0x01)
      {
         // load sub header 1
      }
      else
         System.out.println ("Unknown replay version!");
      
   }
   /**
    *   Get the number of compressed data blocks.
    */
   public int getDataBlocksCount()
   {
      return data_blocks;
   }
   /**
    *   Get the file size after decompression.
    */
   public int getDecompressedSize()
   {
      return decomp_size;
   }
   /**
    *   Get the file size.
    */
   public int getFileSize()
   {
      return file_size;
   }
   /**
    *   Get the replay version.
    */
   public int getReplayVersion()
   {
      return replay_ver;
   }
   /**
    *   Get the header size.
    */
   public int getHeaderSize()
   {
      return head_size;
   }
   /**
    *   Get the file description.
    */
   public char[] getFileDescription()
   {
      return file_desc;
   }
   
}[/code]

WC3RSubHeader.java
[code]package tuberload.wc3.replay;
import java.io.BufferedInputStream;
import java.io.IOException;

public abstract class WC3RSubHeader
{
   protected int unknown_1;      // uknown_1 and patch_ver switch in header1
   protected int patch_ver;
   protected int build_num;
   protected int players_flag;      // single/multiplayer
   protected int replay_len;      // milliseconds
   protected int checksum;
}[/code]

WC3RSubHeader1.java
[code]package tuberload.wc3.replay;

public class WC3RSubHeader1 extends WC3RSubHeader
{
   protected int ver_ident;      // Classic/Expansion
}[/code]

WC3RMain.java
[code]import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import tuberload.wc3.replay.WC3RHeader;
import tuberload.wc3.replay.WC3RSubHeader;

public class WC3Main
{
   public static void main (String[] args)
   {
      try
      {
         BufferedInputStream bos = new BufferedInputStream (
            new FileInputStream ("Jeremy1.w3g"));
         WC3RHeader header = new WC3RHeader (bos);
         
         System.out.println ("File Description: "
            + String.valueOf (header.getFileDescription()));
         System.out.println ("Header Size: " + header.getHeaderSize());
         System.out.println ("File Size: " + header.getFileSize());
         System.out.println ("Decompressed Size: "
            + header.getDecompressedSize());
         System.out.println ("Replay Version: " + header.getReplayVersion());
         System.out.println ("Number of Compressed Data Blocks: "
            + header.getDataBlocksCount());
      }
      catch (IOException exc) { System.out.println (exc.getStackTrace()); }
   }
}[/code]
July 11, 2004, 9:07 AM
UserLoser.
It would be quite an interesting project to write a program to view replays.
July 11, 2004, 4:26 PM
Tuberload
If anyone has a replay from version 1.06 or below it would be very helpful for testing purposes, otherwise I will just reinstall WC3 tonight and make my own.

TIA
July 11, 2004, 7:18 PM
UserLoser.
[quote author=Tuberload link=board=34;threadid=7644;start=0#msg69749 date=1089573508]
If anyone has a replay from version 1.06 or below it would be very helpful for testing purposes, otherwise I will just reinstall WC3 tonight and make my own.

TIA
[/quote]


Why 1.06? I have plenty of 1.16 if you'd like some.

Edit: Nevermind, I see on that document it mentions about 1.06 and below, is different than 1.07 and up.
July 11, 2004, 9:00 PM
Tuberload
[quote author=UserLoser. link=board=34;threadid=7644;start=0#msg69786 date=1089579652]Why 1.06? I have plenty of 1.16 if you'd like some.
[/quote]

The replay file format changes after 1.07 and I wanted to make it backwards compatible. I have 1.16 installed right now so I have no way of making an old replay.
July 11, 2004, 9:03 PM
Tuberload
Code update: it now fully reads the header information and displays it. It is setup to handle old replay files, but I have been unable to test it so far so it might not work correctly.

WC3RHeader.java:
[code]package tuberload.wc3.replay;
import java.io.BufferedInputStream;
import java.io.IOException;
import tuberload.util.Array;
import tuberload.util.ByteBuffer;
/**
*   Constructs the Warcraft III Replay header information.
*/
public class WC3RHeader
{
   // Replay header information
   private char[] file_desc = new char[26];
   private int unknown_1;            // always 0x1A00 so far
   private int head_size;
   private int file_size;
   private int replay_ver;
   private int decomp_size;
   private int data_blocks;         // number of data blocks
   // Subheader information
   private WC3RSubHeader sub_head;      // created based on replay version
   
   /**
    *   Initialize the header.
    */
   public WC3RHeader (BufferedInputStream bos) throws IOException
   {
      // create byte buffer
      ByteBuffer bb = new ByteBuffer();
      byte[] data = new byte[bos.available()];
      bos.read (data, 0, bos.available());
      bb.insertByte (data);
      // get file description
      data = new byte[26];
      data = bb.getBytes (0, 26);
      for (int i = 0; i < 26; i++)
         file_desc[i] = (char)data[i];
      // unknown 1
      unknown_1 = bb.getWord (0x1a);
      // header size/subheader location
      head_size = bb.getDword (0x1c);
      // file size
      file_size = bb.getDword (0x20);
      // replay version
      replay_ver = bb.getDword (0x24);
      // decompressed file size
      decomp_size = bb.getDword (0x28);
      // the number of compressed data blocks
      data_blocks = bb.getDword (0x2c);
      // get the subheader information
      if (replay_ver == 0x00)
      {
         sub_head = new WC3RSubHeader (bb);
      }
      else if (replay_ver == 0x01)
      {
         sub_head = new WC3RSubHeader1 (bb);
      }
      else
         System.out.println ("Unknown replay version!");
      
   }
   /**
    *   Get the subheader information.
    */
   public WC3RSubHeader getSubHeader()
   {
      return sub_head;
   }
   /**
    *   Get the number of compressed data blocks.
    */
   public int getDataBlocksCount()
   {
      return data_blocks;
   }
   /**
    *   Get the file size after decompression.
    */
   public int getDecompressedSize()
   {
      return decomp_size;
   }
   /**
    *   Get the file size.
    */
   public int getFileSize()
   {
      return file_size;
   }
   /**
    *   Get the replay version.
    */
   public int getReplayVersion()
   {
      return replay_ver;
   }
   /**
    *   Get the header size.
    */
   public int getHeaderSize()
   {
      return head_size;
   }
   /**
    *   Get the file description.
    */
   public char[] getFileDescription()
   {
      return file_desc;
   }
}[/code]

WC3RSubHeader.java
[code]package tuberload.wc3.replay;
import tuberload.util.ByteBuffer;

public class WC3RSubHeader
{
   protected int unknown_1;      // uknown_1 and patch_ver switch in sub header1
   protected int patch_ver;
   protected int build_num;
   protected int players_flag;      // single/multiplayer
   protected int replay_len;      // milliseconds
   protected int checksum;
   /**
    *   Initialize the subheader.
    */
   public WC3RSubHeader (ByteBuffer bb)
   {
      unknown_1 = bb.getWord (0x30);
      patch_ver = bb.getWord (0x32);
      build_num = bb.getWord (0x34);
      players_flag = bb.getWord (0x36);
      replay_len = bb.getDword (0x38);
      checksum = bb.getDword (0x3c);
   }
   /**   
    *   Get the replay length.
    */
   public int getReplayLength()
   {
      return replay_len;
   }
   /**
    *   Get the replay checksum.
    */
   public int getChecksum()
   {
      return checksum;
   }
   /**
    *   Get the patch version.
    */
   public int getGamePatchVersion()
   {
      return patch_ver;
   }
   /**
    *   Get the build number.
    */
   public int getGameBuildNumber()
   {
      return build_num;
   }
   /**
    *   Get the number of players flag.
    */
   public boolean isMultiplayer()
   {
      boolean multi = false;
      if (players_flag == 0x8000)
         multi = true;
      return multi;
   }
   /**
    *   Get the game type.
    */
   public boolean isExpansion()
   {
      return false;
   }
}[/code]

WC3RSubHeader1.java
[code]package tuberload.wc3.replay;
import tuberload.util.ByteBuffer;

public class WC3RSubHeader1 extends WC3RSubHeader
{
   protected int ver_ident;      // Classic/Expansion
   /**
    *   Initialize the subheader v1
    */
   public WC3RSubHeader1 (ByteBuffer bb)
   {
      super (bb);
      ver_ident = bb.getDword (0x30);
      patch_ver = bb.getDword (0x34);
      build_num = bb.getWord (0x38);
      players_flag = bb.getWord (0x3a);
      replay_len = bb.getDword (0x3c);
      checksum = bb.getDword (0x40);
   }
   /**
    *   Get the game type.
    */
   public boolean isExpansion()
   {
      boolean expansion = false;
      if (ver_ident != 0x57415233)
         expansion = true;
      return expansion;
   }
}[/code]

WC3Main.java
[code]import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import tuberload.wc3.replay.WC3RHeader;
import tuberload.wc3.replay.WC3RSubHeader;

public class WC3Main
{
   public static void main (String[] args)
   {
      try
      {
         BufferedInputStream bos = new BufferedInputStream (
            new FileInputStream ("Jeremy1.w3g"));
         WC3RHeader header = new WC3RHeader (bos);
         WC3RSubHeader sub_head = header.getSubHeader();
         
         System.out.println ("File Description: "
            + String.valueOf (header.getFileDescription()));
         System.out.println ("Header Size: " + header.getHeaderSize());
         System.out.println ("File Size: " + header.getFileSize());
         System.out.println ("Decompressed Size: "
            + header.getDecompressedSize());
         System.out.println ("Replay Version: " + header.getReplayVersion());
         System.out.println ("Number of Compressed Data Blocks: "
            + header.getDataBlocksCount());
         System.out.println ("Game Patch Version: "
            + sub_head.getGamePatchVersion());
         System.out.println ("Game Build: " + sub_head.getGameBuildNumber());
         System.out.println ("Replay Length: " + sub_head.getReplayLength());
         System.out.println ("Expansion Game:: " + sub_head.isExpansion());
         System.out.println ("Multiplayer: " + sub_head.isMultiplayer());
         System.out.println ("Checksum: " + sub_head.getChecksum());
      }
      catch (IOException exc) { System.out.println (exc.getStackTrace()); }
   }
}[/code]

I believe the replay data is compressed using zlib, so I will work on decompressing and displaying the data blocks next.
July 11, 2004, 9:10 PM
kamakazie
What is the reasoning behind creating your own ByteBuffer?
July 11, 2004, 9:38 PM
Tuberload
[quote author=dxoigmn link=board=34;threadid=7644;start=0#msg69797 date=1089581926]
What is the reasoning behind creating your own ByteBuffer?
[/quote]

I have been working on my own library for quite some time now. No real reasoning behind it other than learning. Basically I do it for shits and giggles and an occasional brain cell addition. ;)
July 11, 2004, 9:40 PM
kamakazie
[quote author=Tuberload link=board=34;threadid=7644;start=0#msg69798 date=1089582023]
[quote author=dxoigmn link=board=34;threadid=7644;start=0#msg69797 date=1089581926]
What is the reasoning behind creating your own ByteBuffer?
[/quote]

I have been working on my own library for quite some time now. No real reasoning behind it other than learning. Basically I do it for shits and giggles and an occasional brain cell addition. ;)
[/quote]

Heh. I was going to say the ByteBuffer in java.nio is pretty robust; but whatever floats your boat ;)
July 11, 2004, 9:41 PM
crashtestdummy
If you know where to get the 1.06 patch from or if 1.00 will work I'll reinstall and run a game for you.

NVM I think I found the patch on the blizzard FTP I'm gonna uninstall and reinstall now.
July 11, 2004, 10:30 PM
Tuberload
[quote author=muert0 link=board=34;threadid=7644;start=0#msg69809 date=1089585030]
If you know where to get the 1.06 patch from or if 1.00 will work I'll reinstall and run a game for you.

NVM I think I found the patch on the blizzard FTP I'm gonna uninstall and reinstall now.
[/quote]

1.00 will work, and thanks.
July 11, 2004, 11:24 PM
Tuberload
Thanks to meurt0 I was able to test it on versions previous to 1.07 and it works fine.
July 11, 2004, 11:53 PM
Tuberload
On a side note to anyone who may be interested, while that documentation is a very good start I have been running a crossed errors and inconsistency in the data so you may have to figure a few things out.

I will be writing my own documentation for the file format as I figure it out. I figure it is a good enough place to start learning such a thing.
July 12, 2004, 1:13 AM
Forged
This is a bump, but did you ever get around to making the viewer? Some people on my forum were asking for one today.
September 3, 2004, 12:48 AM
UserLoser.
I did, but imcomplete.
September 3, 2004, 12:59 AM
Forged
Are you still working on it?
September 3, 2004, 1:22 AM
UserLoser.
Slowly but surely
September 3, 2004, 1:44 AM
Tuberload
I am able to parse the headers (code is available here), and I was able to decompress the data blocks and read in their headers as well. From there is a bunch of Actions and such that you use to display a replay. From what I understand there is no actual movie formatted data saved anywhere?

I will release an updated version in Java shortly. Maybe from there I can make my own simple 2D player.
September 3, 2004, 3:36 AM
Forged
from what I understand the replays only excute the action which is why the 1.07+ are diffrent.

So unless you drew from the game I have no idea how you would make a movie maker. I am not a very smart person though so I have no idea.
September 3, 2004, 11:06 PM
Tuberload
What I meant is there is no compressed movie data present in the replay files so you can not just pull out the data and pass it to a media player. You have to produce the movie yourself based on the actions present in the replay I believe.

I am by no means an expert on this subject, but during the brief period I worked on it that is what I discovered.

Although there could possibly be a library present that will play the movie when the actions are passed to it. This is of course complete speculation due to the fact that I have not looked into it, and currently do not posses the knowledge required to find out.
September 4, 2004, 9:12 PM
UserLoser.
Correct, if you want to have a movie, then you'll have to come up with the graphics to cooperate with the actions in the replay file (Which would be a lot of work).
September 4, 2004, 10:12 PM
Adron
Why not just make something to capture movies from War3?
September 9, 2004, 9:20 PM
Noodlez
Couldn't you just display the units from the MPQ files?
September 15, 2004, 3:01 AM
UserLoser.
[quote author=Noodlez link=board=34;threadid=7644;start=15#msg80206 date=1095217312]
Couldn't you just display the units from the MPQ files?
[/quote]

If you feel like figuring that out and sharing it with us, ok :)
September 15, 2004, 7:27 PM
Myndfyr
[quote author=Noodlez link=board=34;threadid=7644;start=15#msg80206 date=1095217312]
Couldn't you just display the units from the MPQ files?
[/quote]

At that point, how much harder is it to just make another Warcraft-III game? :P
September 16, 2004, 12:36 AM
Tuberload
[quote author=MyndFyre link=board=34;threadid=7644;start=15#msg80342 date=1095295002]
[quote author=Noodlez link=board=34;threadid=7644;start=15#msg80206 date=1095217312]
Couldn't you just display the units from the MPQ files?
[/quote]

At that point, how much harder is it to just make another Warcraft-III game? :P
[/quote]

If I were to try and make my own replay player it would use crude 2D images that looked nothing like they should. It would be up to the user and my documentation to figure the rest out. :) Other than that I think Adron has the best solution.
September 17, 2004, 10:36 PM
Noodlez
It's not hard at all... there are open source programs that do it already.
September 18, 2004, 10:31 PM
Myndfyr
[quote author=Noodlez link=board=34;threadid=7644;start=15#msg80762 date=1095546666]
It's not hard at all... there are open source programs that do it already.
[/quote]

Such as?

By the way, my suggestion in re: to the comment, "Couldn't you just display the units from the MPQ files?" was that, if you're going to go through the trouble to display the fully-3D graphics extracted from the MPQ files, you might as well make the full game, because you're already practically there.

But that's dumb. :P
September 20, 2004, 8:14 AM
Tuberload
Minus all of the AI algorithms and such.
September 20, 2004, 8:35 PM

Search