w3g

package
v1.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 13, 2022 License: MPL-2.0 Imports: 14 Imported by: 3

Documentation

Overview

Package w3g implements a decoder and encoder for w3g files.

Each record type is mapped to a struct type that implements the Record interface. To open a file, use w3g.Open().

Format:

 size/type | Description
-----------+-----------------------------------------------------------
 28 chars  | zero terminated string "Warcraft III recorded game\0x1A\0"
  1 dword  | fileoffset of first compressed data block (header size)
           |  0x40 for WarCraft III with patch <= v1.06
           |  0x44 for WarCraft III patch >= 1.07 and TFT replays
  1 dword  | overall size of compressed file
  1 dword  | replay header version:
           |  0x00 for WarCraft III with patch <= 1.06
           |  0x01 for WarCraft III patch >= 1.07 and TFT replays
  1 dword  | overall size of decompressed data (excluding header)
  1 dword  | number of compressed data blocks in file

* replay header version 0x00:
     1  word  | unknown (always zero so far)
     1  word  | version number (corresponds to patch 1.xx)
* replay header version 0x01:
     1 dword  | version identifier string reading:
              |  'WAR3' for WarCraft III Classic
              |  'W3XP' for WarCraft III Expansion Set 'The Frozen Throne'
     1 dword  | version number (corresponds to patch 1.xx so far)

  1  word  | build number
  1  word  | flags
           |   0x0000 for single player games
           |   0x8000 for multiplayer games (LAN or Battle.net)
  1 dword  | replay length in msec
  1 dword  | CRC32 checksum for the header
           | (the checksum is calculated for the complete header
           |  including this field which is set to zero)

For each data block:
  1  word  | size n of compressed data block (excluding header)
  1  word  | size of decompressed data block (currently 8k)
  1  word  | CRC checksum for the header
  1  word  | CRC checksum for the compressed block
  n bytes  | compressed data (using zlib)
Example
package main

import (
	"fmt"

	"github.com/nielsAD/gowarcraft3/file/w3g"
)

func main() {
	replay, err := w3g.Open("./test_130.w3g")
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(replay.HostPlayer.Name)

}
Output:

niels

Index

Examples

Constants

View Source
const (
	RidGameInfo       = 0x10
	RidPlayerInfo     = 0x16 // w3gs.PlayerInfo
	RidPlayerLeft     = 0x17 // w3gs.Leave
	RidSlotInfo       = 0x19 // w3gs.SlotInfo
	RidCountDownStart = 0x1A // w3gs.CountDownStart
	RidCountDownEnd   = 0x1B // w3gs.CountDownEnd
	RidGameStart      = 0x1C
	RidTimeSlot2      = 0x1E // w3gs.TimeSlot2 (fragment)
	RidTimeSlot       = 0x1F // w3gs.TimeSlot2
	RidChatMessage    = 0x20 // w3gs.MessageRelay
	RidTimeSlotAck    = 0x22 // w3gs.TimeSlotAck
	RidDesync         = 0x23
	RidEndTimer       = 0x2F
	RidPlayerExtra    = 0x39
)

Record type identifiers

Variables

View Source
var (
	ErrBadFormat       = errors.New("w3g: Invalid file format")
	ErrInvalidChecksum = errors.New("w3g: Checksum invalid")
	ErrUnexpectedConst = errors.New("w3g: Unexpected constant value")
	ErrUnknownRecord   = errors.New("w3g: Unknown record ID")
)

Errors

View Source
var DefaultFactory = MapFactory{
	RidGameInfo:       func(_ *Encoding) Record { return &GameInfo{} },
	RidPlayerInfo:     func(_ *Encoding) Record { return &PlayerInfo{} },
	RidPlayerLeft:     func(_ *Encoding) Record { return &PlayerLeft{} },
	RidSlotInfo:       func(_ *Encoding) Record { return &SlotInfo{} },
	RidCountDownStart: func(_ *Encoding) Record { return &CountDownStart{} },
	RidCountDownEnd:   func(_ *Encoding) Record { return &CountDownEnd{} },
	RidGameStart:      func(_ *Encoding) Record { return &GameStart{} },
	RidTimeSlot2:      func(_ *Encoding) Record { return &TimeSlot{} },
	RidTimeSlot:       func(_ *Encoding) Record { return &TimeSlot{} },
	RidChatMessage: func(e *Encoding) Record {
		if e.GameVersion == 0 || e.GameVersion > 2 {
			return &ChatMessage{}
		}
		return &TimeSlotAck{}
	},
	RidTimeSlotAck: func(_ *Encoding) Record { return &TimeSlotAck{} },
	RidDesync:      func(_ *Encoding) Record { return &Desync{} },
	RidEndTimer:    func(_ *Encoding) Record { return &EndTimer{} },
	RidPlayerExtra: func(_ *Encoding) Record { return &PlayerExtra{} },
}

DefaultFactory maps record ID to matching type

View Source
var Signature = "Warcraft III recorded game\x1A"

Signature constant for w3g files

Functions

func DecodeHeader

func DecodeHeader(r io.Reader, f RecordFactory) (*Header, *Decompressor, int, error)

DecodeHeader a w3g file, returns header and a Decompressor to read compressed records

func FindHeader

func FindHeader(r Peeker) (int, error)

FindHeader in r

func SerializeRecord

func SerializeRecord(r Record, e Encoding) ([]byte, error)

SerializeRecord serializes r and returns its byte representation.

func WriteRecord

func WriteRecord(w io.Writer, r Record, e Encoding) (int, error)

WriteRecord serializes r and writes it to w.

Types

type BlockCompressor

type BlockCompressor struct {
	Encoding

	SizeWritten uint32 // Compressed size written in total
	SizeTotal   uint32 // Decompressed size written in total
	NumBlocks   uint32 // Blocks written in total
	// contains filtered or unexported fields
}

BlockCompressor is an io.Writer that compresses data blocks

func NewBlockCompressor

func NewBlockCompressor(w io.Writer, e Encoding) *BlockCompressor

NewBlockCompressor for compressed w3g data

func (*BlockCompressor) Write

func (d *BlockCompressor) Write(b []byte) (int, error)

Write implements the io.Writer interface.

type CacheFactory

type CacheFactory struct {
	// contains filtered or unexported fields
}

CacheFactory implements a RecordFactory that will only create a type once

func (CacheFactory) NewRecord

func (f CacheFactory) NewRecord(rid uint8, enc *Encoding) Record

NewRecord implements RecordFactory interface

type ChatMessage

type ChatMessage struct {
	w3gs.Message
}

ChatMessage record [0x20]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
  1 byte   | PlayerID (message sender)
  1 word   | n = number of bytes that follow
  1 byte   | flags
           |   0x10   for delayed startup screen messages
           |   0x20   for normal messages
  1 dword  | chat mode (not present if flag = 0x10):
           |   0x00   for messages to all players
           |   0x01   for messages to allies
           |   0x02   for messages to observers or referees
           |   0x03+N for messages to specific player N (with N = slotnumber)
  n bytes  | zero terminated string containing the text message

func (*ChatMessage) Deserialize

func (rec *ChatMessage) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*ChatMessage) Serialize

func (rec *ChatMessage) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type Compressor

type Compressor struct {
	RecordEncoder
	*BlockCompressor
	*bufio.Writer
}

Compressor is an io.Writer that compresses buffered data blocks

func NewCompressor

func NewCompressor(w io.Writer, e Encoding) *Compressor

NewCompressor for compressed w3g with default buffer size

func NewCompressorSize

func NewCompressorSize(w io.Writer, e Encoding, size int) *Compressor

NewCompressorSize for compressed w3g with specified buffer size

func (*Compressor) Close

func (d *Compressor) Close() error

Close adds padding to fill last block and flushes any buffered data

func (*Compressor) Write

func (d *Compressor) Write(p []byte) (int, error)

Write implements the io.Writer interface.

func (*Compressor) WriteRecord

func (d *Compressor) WriteRecord(r Record) (int, error)

WriteRecord serializes r and writes it to d

func (*Compressor) WriteRecords

func (d *Compressor) WriteRecords(r ...Record) (int, error)

WriteRecords serializes r and writes to d

type CountDownEnd

type CountDownEnd struct {
	GameStart
}

CountDownEnd record [0x1B]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
   1 dword | unknown (always 0x01 so far)

func (*CountDownEnd) Serialize

func (rec *CountDownEnd) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type CountDownStart

type CountDownStart struct {
	GameStart
}

CountDownStart record [0x1A]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
   1 dword | unknown (always 0x01 so far)

func (*CountDownStart) Serialize

func (rec *CountDownStart) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type Decompressor

type Decompressor struct {
	RecordDecoder

	SizeRead  uint32 // Compressed size read in total
	SizeTotal uint32 // Decompressed size left to read in total
	SizeBlock uint32 // Decompressed size left to read current block
	NumBlocks uint32 // Blocks left to read
	// contains filtered or unexported fields
}

Decompressor is an io.Reader that decompresses data blocks

func NewDecompressor

func NewDecompressor(r io.Reader, e Encoding, f RecordFactory, numBlocks uint32, sizeTotal uint32) *Decompressor

NewDecompressor for compressed w3g data

func (*Decompressor) ForEach

func (d *Decompressor) ForEach(f func(r Record) error) error

ForEach record call f

func (*Decompressor) Read

func (d *Decompressor) Read(b []byte) (int, error)

Read implements the io.Reader interface.

type Desync

type Desync struct {
	w3gs.Desync
}

Desync record [0x23]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
   1 dword | unknown
   1 byte  | unknown (always 4?)
   1 dword | unknown (random?)
   1 byte  | unknown (always 0?)

func (*Desync) Deserialize

func (rec *Desync) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*Desync) Serialize

func (rec *Desync) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type Encoder

type Encoder struct {
	Header
	*Compressor
	// contains filtered or unexported fields
}

Encoder compresses records and updates header on close

func NewEncoder

func NewEncoder(w io.Writer, e Encoding) (*Encoder, error)

NewEncoder for replay file

func (*Encoder) Close

func (e *Encoder) Close() error

Close writer, flush data, and update header. Does not close underlying writer.

type Encoding

type Encoding struct {
	w3gs.Encoding
}

Encoding options for (de)serialization

type EndTimer

type EndTimer struct {
	GameOver     bool
	CountDownSec uint32
}

EndTimer record [0x2F]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
   1 dword | mode:
           |   0x00 countdown is running
           |   0x01 countdown is over (end is forced *now*)
   1 dword | countdown time in sec

func (*EndTimer) Deserialize

func (rec *EndTimer) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*EndTimer) Serialize

func (rec *EndTimer) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type FactoryFunc

type FactoryFunc func(enc *Encoding) Record

FactoryFunc creates new Record

type GameInfo

type GameInfo struct {
	HostPlayer   PlayerInfo
	GameName     string
	GameSettings w3gs.GameSettings
	GameFlags    w3gs.GameFlags
	NumSlots     uint32
	LanguageID   uint32
}

GameInfo record [0x10]

Format:

    Size   | Name
-----------+--------------------------
    4 byte | Number of host records
  variable | PlayerInfo for host
  variable | GameName (null terminated string)
    1 byte | Nullbyte
  variable | Encoded String (null terminated)
           |  - GameSettings
           |  - Map&CreatorName
    4 byte | PlayerCount
    4 byte | GameType
    4 byte | LanguageID

func (*GameInfo) Deserialize

func (rec *GameInfo) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*GameInfo) Serialize

func (rec *GameInfo) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type GameStart

type GameStart struct{}

GameStart record [0x1C]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
   1 dword | unknown (always 0x01 so far)

func (*GameStart) Deserialize

func (rec *GameStart) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*GameStart) Serialize

func (rec *GameStart) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type Header struct {
	GameVersion  w3gs.GameVersion
	BuildNumber  uint16
	DurationMS   uint32
	SinglePlayer bool
}

Header for a Warcraft III recorded game file

func (*Header) Encoding

func (h *Header) Encoding() Encoding

Encoding for (de)serialization

type MapFactory

type MapFactory map[uint8]FactoryFunc

MapFactory implements RecordFactory using a map

func (MapFactory) NewRecord

func (f MapFactory) NewRecord(rid uint8, enc *Encoding) Record

NewRecord implements RecordFactory interface

type Peeker

type Peeker interface {
	Peek(n int) ([]byte, error)
	Discard(n int) (discarded int, err error)
}

Peeker is the interface that wraps basic Peek and Discard methods.

There is no way to determine record size without reading the full record, so it's impossible to know how many bytes should be read from an io.Reader. Instead, we peek a certain amount of bytes, deserialize the record, and then discard the actual amount of bytes read.

This interface is implemented by bufio.Reader, for example.

type PlayerExtra added in v1.6.0

type PlayerExtra struct {
	w3gs.PlayerExtra
}

PlayerExtra record [0x39]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
   1 byte  | sub type (0x03)
           |   0x03   battle.net profile data
           |   0x04   in-game skins
   1 dword | number of bytes following
   n bytes | protobuf encoded struct

For each battle.net profile (sub type 0x03, encoded with protobuf):
   1 byte  | player ID
   string  | battletag
   string  | clan
   string  | portrait
   1 byte  | team
   string  | unknown

For each player (sub type 0x04, encoded with protobuf):
   1 byte  | player ID
   For each in-game skin:
   qword   | unit ID
   qword   | skin ID
   string  | skin collection

func (*PlayerExtra) Deserialize added in v1.6.0

func (rec *PlayerExtra) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*PlayerExtra) Serialize added in v1.6.0

func (rec *PlayerExtra) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type PlayerInfo

type PlayerInfo struct {
	ID          uint8
	Name        string
	Race        w3gs.RacePref
	JoinCounter uint32
}

PlayerInfo record [0x16]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
  1 byte   | PlayerID
  n bytes  | PlayerName (null terminated string)
  1 byte   | size of additional data:
           |  0x01 = custom
           |  0x08 = ladder

* If custom (0x01):
    1 byte    | null byte (1 byte)
* If ladder (0x08):
    4 bytes   | runtime of player's Warcraft.exe in milliseconds
    4 bytes   | player race flags:
              |   0x01=human
              |   0x02=orc
              |   0x04=nightelf
              |   0x08=undead
              |  (0x10=daemon)
              |   0x20=random
              |   0x40=race selectable/fixed

func (*PlayerInfo) Deserialize

func (rec *PlayerInfo) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*PlayerInfo) DeserializeContent

func (rec *PlayerInfo) DeserializeContent(buf *protocol.Buffer, enc *Encoding) error

DeserializeContent decodes the binary data generated by SerializeContent.

func (*PlayerInfo) Serialize

func (rec *PlayerInfo) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

func (*PlayerInfo) SerializeContent

func (rec *PlayerInfo) SerializeContent(buf *protocol.Buffer, enc *Encoding)

SerializeContent encodes the struct into its binary form without record ID.

type PlayerLeft

type PlayerLeft struct {
	Local    bool
	PlayerID uint8
	Reason   w3gs.LeaveReason
	Counter  uint32
}

PlayerLeft record [0x17]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
   1 dword | reason
           |  0x01 - connection closed by remote game
           |  0x0C - connection closed by local game
           |  0x0E - unknown (rare) (almost like 0x01)
   1 byte  | PlayerID
   1 dword | result - see table below
   1 dword | unknown (number of replays saved this warcraft session?)

func (*PlayerLeft) Deserialize

func (rec *PlayerLeft) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*PlayerLeft) Serialize

func (rec *PlayerLeft) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type Record

type Record interface {
	Serialize(buf *protocol.Buffer, enc *Encoding) error
	Deserialize(buf *protocol.Buffer, enc *Encoding) error
}

Record interface.

func DeserializeRecord

func DeserializeRecord(b []byte, e Encoding) (Record, int, error)

DeserializeRecord reads exactly one record from b and returns it in the proper (deserialized) record type.

func ReadRecord

func ReadRecord(r Peeker, e Encoding) (Record, int, error)

ReadRecord reads one record from r and returns it in the proper (deserialized) record type.

type RecordDecoder

type RecordDecoder struct {
	Encoding
	RecordFactory
	// contains filtered or unexported fields
}

RecordDecoder keeps amortized allocs at 0 for repeated Record.Deserialize calls.

func NewRecordDecoder

func NewRecordDecoder(e Encoding, f RecordFactory) *RecordDecoder

NewRecordDecoder initialization

func (*RecordDecoder) Deserialize

func (dec *RecordDecoder) Deserialize(b []byte) (Record, int, error)

Deserialize reads exactly one record from b and returns it in the proper (deserialized) record type.

func (*RecordDecoder) Read

func (dec *RecordDecoder) Read(r Peeker) (Record, int, error)

Read exactly one record from r and returns it in the proper (deserialized) record type.

type RecordEncoder

type RecordEncoder struct {
	Encoding
	// contains filtered or unexported fields
}

RecordEncoder keeps amortized allocs at 0 for repeated Record.Serialize calls Byte slices are valid until the next Serialize() call

func NewRecordEncoder

func NewRecordEncoder(e Encoding) *RecordEncoder

NewRecordEncoder initialization

func (*RecordEncoder) Serialize

func (enc *RecordEncoder) Serialize(r Record) ([]byte, error)

Serialize record and returns its byte representation. Result is valid until the next Serialize() call.

func (*RecordEncoder) Write

func (enc *RecordEncoder) Write(w io.Writer, r Record) (int, error)

Write serializes r and writes it to w.

type RecordFactory

type RecordFactory interface {
	NewRecord(rid uint8, enc *Encoding) Record
}

RecordFactory returns a struct of the appropiate type for a record ID

func NewFactoryCache

func NewFactoryCache(factory RecordFactory) RecordFactory

NewFactoryCache initializes CacheFactory

type Replay

type Replay struct {
	Header
	GameInfo
	SlotInfo
	PlayerInfo  []*PlayerInfo
	PlayerExtra []*PlayerExtra
	Records     []Record
}

Replay information for Warcraft III recorded game

func Decode

func Decode(r io.Reader) (*Replay, error)

Decode a w3g file

func Open

func Open(name string) (*Replay, error)

Open a w3g file

func (*Replay) Encode

func (r *Replay) Encode(w io.Writer) error

Encode to w

func (*Replay) Save

func (r *Replay) Save(name string) error

Save a w3g file

type SlotInfo

type SlotInfo struct {
	w3gs.SlotInfo
}

SlotInfo record [0x19]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
  1 word   | number of data bytes following
  1 byte   | nr of SlotRecords following (== nr of slots on startscreen)
  n bytes  | nr * SlotRecord
  1 dword  | RandomSeed
  1 byte   | SelectMode
           |   0x00 - team & race selectable (for standard custom games)
           |   0x01 - team not selectable
           |          (map setting: fixed alliances in WorldEditor)
           |   0x03 - team & race not selectable
           |          (map setting: fixed player properties in WorldEditor)
           |   0x04 - race fixed to random
           |          (extended map options: random races selected)
           |   0xcc - Automated Match Making (ladder)
  1 byte   | StartSpotCount (nr. of start positions in map)

For each slot:
  1 byte   | player id (0x00 for computer players)
  1 byte   | map download percent: 0x64 in custom, 0xff in ladder
  1 byte   | slotstatus:
           |   0x00 empty slot
           |   0x01 closed slot
           |   0x02 used slot
  1 byte   | computer player flag:
           |   0x00 for human player
           |   0x01 for computer player
  1 byte   | team number:0 - 11
           | (team 12 == observer or referee)
  1 byte   | color (0-11):
           |   value+1 matches player colors in world editor:
           |   (red, blue, cyan, purple, yellow, orange, green,
           |    pink, gray, light blue, dark green, brown)
           |   color 12 == observer or referee
  1 byte   | player race flags (as selected on map screen):
           |   0x01=human
           |   0x02=orc
           |   0x04=nightelf
           |   0x08=undead
           |   0x20=random
           |   0x40=race selectable/fixed
  1 byte   | computer AI strength: (only present in v1.03 or higher)
           |   0x00 for easy
           |   0x01 for normal
           |   0x02 for insane
           | for non-AI players this seems to be always 0x01
  1 byte   | player handicap in percent (as displayed on startscreen)
           | valid values: 0x32, 0x3C, 0x46, 0x50, 0x5A, 0x64
           | (field only present in v1.07 or higher)

func (*SlotInfo) Deserialize

func (rec *SlotInfo) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*SlotInfo) Serialize

func (rec *SlotInfo) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type TimeSlot

type TimeSlot struct {
	w3gs.TimeSlot
}

TimeSlot record [0x1E] / [0x1F]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
  1 word   | n = number of bytes that follow
  1 word   | time increment (milliseconds)
           |   about 250 ms in battle.net
           |   about 100 ms in LAN and single player
  n-2 byte | CommandData block(s) (not present if n=2)

func (*TimeSlot) Deserialize

func (rec *TimeSlot) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*TimeSlot) Serialize

func (rec *TimeSlot) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

type TimeSlotAck

type TimeSlotAck struct {
	Checksum []byte
}

TimeSlotAck record [0x22]

Format:

 size/type | Description
-----------+-----------------------------------------------------------
   1 byte  | number of bytes following (always 0x04 so far)
   1 dword | checksum

func (*TimeSlotAck) Deserialize

func (rec *TimeSlotAck) Deserialize(buf *protocol.Buffer, enc *Encoding) error

Deserialize decodes the binary data generated by Serialize.

func (*TimeSlotAck) Serialize

func (rec *TimeSlotAck) Serialize(buf *protocol.Buffer, enc *Encoding) error

Serialize encodes the struct into its binary form.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL