d2datautil.StreamReader refactor

*`StreamReader.Read` methods now return errors

The other edits in this commit are related to cleaning up lint errors
caused by the changes to StreamReader
This commit is contained in:
gravestench 2021-01-11 17:23:43 -08:00
parent 938ce20579
commit 87d531814d
15 changed files with 1035 additions and 330 deletions

View File

@ -150,7 +150,10 @@ func (a *App) initAnimationData(path string) error {
a.Debugf(fmtLoadAnimData, path) a.Debugf(fmtLoadAnimData, path)
animData := d2data.LoadAnimationData(animDataBytes) animData, err := d2data.LoadAnimationData(animDataBytes)
if err != nil {
a.Error(err.Error())
}
a.Infof("Loaded %d animation data records", len(animData)) a.Infof("Loaded %d animation data records", len(animData))

View File

@ -27,20 +27,48 @@ type AnimationDataRecord struct {
type AnimationData map[string][]*AnimationDataRecord type AnimationData map[string][]*AnimationDataRecord
// LoadAnimationData loads the animation data table into the global AnimationData dictionary // LoadAnimationData loads the animation data table into the global AnimationData dictionary
func LoadAnimationData(rawData []byte) AnimationData { func LoadAnimationData(rawData []byte) (AnimationData, error) {
animdata := make(AnimationData) animdata := make(AnimationData)
streamReader := d2datautils.CreateStreamReader(rawData) streamReader := d2datautils.CreateStreamReader(rawData)
for !streamReader.EOF() { for !streamReader.EOF() {
dataCount := int(streamReader.GetInt32()) var dataCount int
b, err := streamReader.ReadInt32()
if err != nil {
return nil, err
}
dataCount = int(b)
for i := 0; i < dataCount; i++ { for i := 0; i < dataCount; i++ {
cofNameBytes := streamReader.ReadBytes(numCofNameBytes) cofNameBytes, err := streamReader.ReadBytes(numCofNameBytes)
if err != nil {
return nil, err
}
fpd, err := streamReader.ReadInt32()
if err != nil {
return nil, err
}
animSpeed, err := streamReader.ReadInt32()
if err != nil {
return nil, err
}
data := &AnimationDataRecord{ data := &AnimationDataRecord{
COFName: strings.ReplaceAll(string(cofNameBytes), string(byte(0)), ""), COFName: strings.ReplaceAll(string(cofNameBytes), string(byte(0)), ""),
FramesPerDirection: int(streamReader.GetInt32()), FramesPerDirection: int(fpd),
AnimationSpeed: int(streamReader.GetInt32()), AnimationSpeed: int(animSpeed),
} }
data.Flags = streamReader.ReadBytes(numFlagBytes)
flagBytes, err := streamReader.ReadBytes(numFlagBytes)
if err != nil {
return nil, err
}
data.Flags = flagBytes
cofIndex := strings.ToLower(data.COFName) cofIndex := strings.ToLower(data.COFName)
if _, found := animdata[cofIndex]; !found { if _, found := animdata[cofIndex]; !found {
@ -51,5 +79,5 @@ func LoadAnimationData(rawData []byte) AnimationData {
} }
} }
return animdata return animdata, nil
} }

View File

@ -6,7 +6,7 @@ import (
// WavDecompress decompresses wav files // WavDecompress decompresses wav files
//nolint:gomnd // binary decode magic //nolint:gomnd // binary decode magic
func WavDecompress(data []byte, channelCount int) []byte { //nolint:funlen,gocognit,gocyclo // can't reduce func WavDecompress(data []byte, channelCount int) ([]byte, error) { //nolint:funlen,gocognit,gocyclo // can't reduce
Array1 := []int{0x2c, 0x2c} Array1 := []int{0x2c, 0x2c}
Array2 := make([]int, channelCount) Array2 := make([]int, channelCount)
@ -35,20 +35,33 @@ func WavDecompress(data []byte, channelCount int) []byte { //nolint:funlen,gocog
input := d2datautils.CreateStreamReader(data) input := d2datautils.CreateStreamReader(data)
output := d2datautils.CreateStreamWriter() output := d2datautils.CreateStreamWriter()
input.GetByte() _, err := input.ReadByte()
if err != nil {
return nil, err
}
shift := input.GetByte() shift, err := input.ReadByte()
if err != nil {
return nil, err
}
for i := 0; i < channelCount; i++ { for i := 0; i < channelCount; i++ {
temp := input.GetInt16() temp, err := input.ReadInt16()
if err != nil {
return nil, err
}
Array2[i] = int(temp) Array2[i] = int(temp)
output.PushInt16(temp) output.PushInt16(temp)
} }
channel := channelCount - 1 channel := channelCount - 1
for input.GetPosition() < input.GetSize() { for input.Position() < input.Size() {
value := input.GetByte() value, err := input.ReadByte()
if err != nil {
return nil, err
}
if channelCount == 2 { if channelCount == 2 {
channel = 1 - channel channel = 1 - channel
@ -129,5 +142,5 @@ func WavDecompress(data []byte, channelCount int) []byte { //nolint:funlen,gocog
} }
} }
return output.GetBytes() return output.GetBytes(), nil
} }

View File

@ -1,6 +1,7 @@
package d2video package d2video
import ( import (
"errors"
"log" "log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
@ -29,6 +30,12 @@ const (
BinkVideoModeWidthAndHeightInterlaced BinkVideoModeWidthAndHeightInterlaced
) )
const (
numHeaderBytes = 3
bikHeaderStr = "BIK"
numAudioTrackUnknownBytes = 2
)
// BinkAudioAlgorithm represents the type of bink audio algorithm // BinkAudioAlgorithm represents the type of bink audio algorithm
type BinkAudioAlgorithm uint32 type BinkAudioAlgorithm uint32
@ -72,75 +79,157 @@ type BinkDecoder struct {
} }
// CreateBinkDecoder returns a new instance of the bink decoder // CreateBinkDecoder returns a new instance of the bink decoder
func CreateBinkDecoder(source []byte) *BinkDecoder { func CreateBinkDecoder(source []byte) (*BinkDecoder, error) {
result := &BinkDecoder{ result := &BinkDecoder{
streamReader: d2datautils.CreateStreamReader(source), streamReader: d2datautils.CreateStreamReader(source),
} }
result.loadHeaderInformation() err := result.loadHeaderInformation()
return result return result, err
} }
// GetNextFrame gets the next frame // GetNextFrame gets the next frame
func (v *BinkDecoder) GetNextFrame() { func (v *BinkDecoder) GetNextFrame() error {
//nolint:gocritic // v.streamReader.SetPosition(uint64(v.FrameIndexTable[i] & 0xFFFFFFFE)) //nolint:gocritic // v.streamReader.SetPosition(uint64(v.FrameIndexTable[i] & 0xFFFFFFFE))
lengthOfAudioPackets := v.streamReader.GetUInt32() - 4 //nolint:gomnd // decode magic lengthOfAudioPackets, err := v.streamReader.ReadUInt32()
samplesInPacket := v.streamReader.GetUInt32() if err != nil {
return err
}
v.streamReader.SkipBytes(int(lengthOfAudioPackets)) samplesInPacket, err := v.streamReader.ReadUInt32()
if err != nil {
return err
}
v.streamReader.SkipBytes(int(lengthOfAudioPackets) - 4) //nolint:gomnd // decode magic
log.Printf("Frame %d:\tSamp: %d", v.frameIndex, samplesInPacket) log.Printf("Frame %d:\tSamp: %d", v.frameIndex, samplesInPacket)
v.frameIndex++ v.frameIndex++
return nil
} }
//nolint:gomnd // Decoder magic //nolint:gomnd,funlen,gocyclo // Decoder magic, can't help the long function length for now
func (v *BinkDecoder) loadHeaderInformation() { func (v *BinkDecoder) loadHeaderInformation() error {
v.streamReader.SetPosition(0) v.streamReader.SetPosition(0)
headerBytes := v.streamReader.ReadBytes(3)
if string(headerBytes) != "BIK" { var err error
log.Fatal("Invalid header for bink video")
headerBytes, err := v.streamReader.ReadBytes(numHeaderBytes)
if err != nil {
return err
}
if string(headerBytes) != bikHeaderStr {
return errors.New("invalid header for bink video")
}
v.videoCodecRevision, err = v.streamReader.ReadByte()
if err != nil {
return err
}
v.fileSize, err = v.streamReader.ReadUInt32()
if err != nil {
return err
}
v.numberOfFrames, err = v.streamReader.ReadUInt32()
if err != nil {
return err
}
v.largestFrameSizeBytes, err = v.streamReader.ReadUInt32()
if err != nil {
return err
}
const numBytesToSkip = 4 // Number of frames again?
v.streamReader.SkipBytes(numBytesToSkip)
v.VideoWidth, err = v.streamReader.ReadUInt32()
if err != nil {
return err
}
v.VideoHeight, err = v.streamReader.ReadUInt32()
if err != nil {
return err
}
fpsDividend, err := v.streamReader.ReadUInt32()
if err != nil {
return err
}
fpsDivider, err := v.streamReader.ReadUInt32()
if err != nil {
return err
} }
v.videoCodecRevision = v.streamReader.GetByte()
v.fileSize = v.streamReader.GetUInt32()
v.numberOfFrames = v.streamReader.GetUInt32()
v.largestFrameSizeBytes = v.streamReader.GetUInt32()
v.streamReader.SkipBytes(4) // Number of frames again?
v.VideoWidth = v.streamReader.GetUInt32()
v.VideoHeight = v.streamReader.GetUInt32()
fpsDividend := v.streamReader.GetUInt32()
fpsDivider := v.streamReader.GetUInt32()
v.FPS = uint32(float32(fpsDividend) / float32(fpsDivider)) v.FPS = uint32(float32(fpsDividend) / float32(fpsDivider))
v.FrameTimeMS = 1000 / v.FPS v.FrameTimeMS = 1000 / v.FPS
videoFlags := v.streamReader.GetUInt32()
videoFlags, err := v.streamReader.ReadUInt32()
if err != nil {
return err
}
v.VideoMode = BinkVideoMode((videoFlags >> 28) & 0x0F) v.VideoMode = BinkVideoMode((videoFlags >> 28) & 0x0F)
v.HasAlphaPlane = ((videoFlags >> 20) & 0x1) == 1 v.HasAlphaPlane = ((videoFlags >> 20) & 0x1) == 1
v.Grayscale = ((videoFlags >> 17) & 0x1) == 1 v.Grayscale = ((videoFlags >> 17) & 0x1) == 1
numberOfAudioTracks := v.streamReader.GetUInt32()
numberOfAudioTracks, err := v.streamReader.ReadUInt32()
if err != nil {
return err
}
v.AudioTracks = make([]BinkAudioTrack, numberOfAudioTracks) v.AudioTracks = make([]BinkAudioTrack, numberOfAudioTracks)
for i := 0; i < int(numberOfAudioTracks); i++ { for i := 0; i < int(numberOfAudioTracks); i++ {
v.streamReader.SkipBytes(2) // Unknown v.streamReader.SkipBytes(numAudioTrackUnknownBytes)
v.AudioTracks[i].AudioChannels = v.streamReader.GetUInt16()
v.AudioTracks[i].AudioChannels, err = v.streamReader.ReadUInt16()
if err != nil {
return err
}
} }
for i := 0; i < int(numberOfAudioTracks); i++ { for i := 0; i < int(numberOfAudioTracks); i++ {
v.AudioTracks[i].AudioSampleRateHz = v.streamReader.GetUInt16() v.AudioTracks[i].AudioSampleRateHz, err = v.streamReader.ReadUInt16()
flags := v.streamReader.GetUInt16() if err != nil {
return err
}
var flags uint16
flags, err = v.streamReader.ReadUInt16()
if err != nil {
return err
}
v.AudioTracks[i].Stereo = ((flags >> 13) & 0x1) == 1 v.AudioTracks[i].Stereo = ((flags >> 13) & 0x1) == 1
v.AudioTracks[i].Algorithm = BinkAudioAlgorithm((flags >> 12) & 0x1) v.AudioTracks[i].Algorithm = BinkAudioAlgorithm((flags >> 12) & 0x1)
} }
for i := 0; i < int(numberOfAudioTracks); i++ { for i := 0; i < int(numberOfAudioTracks); i++ {
v.AudioTracks[i].AudioTrackID = v.streamReader.GetUInt32() v.AudioTracks[i].AudioTrackID, err = v.streamReader.ReadUInt32()
if err != nil {
return err
}
} }
v.FrameIndexTable = make([]uint32, v.numberOfFrames+1) v.FrameIndexTable = make([]uint32, v.numberOfFrames+1)
for i := 0; i < int(v.numberOfFrames+1); i++ { for i := 0; i < int(v.numberOfFrames+1); i++ {
v.FrameIndexTable[i] = v.streamReader.GetUInt32() v.FrameIndexTable[i], err = v.streamReader.ReadUInt32()
if err != nil {
return err
}
} }
return nil
} }

View File

@ -4,6 +4,12 @@ import (
"io" "io"
) )
const (
bytesPerint16 = 2
bytesPerint32 = 4
bytesPerint64 = 8
)
// StreamReader allows you to read data from a byte array in various formats // StreamReader allows you to read data from a byte array in various formats
type StreamReader struct { type StreamReader struct {
data []byte data []byte
@ -20,53 +26,72 @@ func CreateStreamReader(source []byte) *StreamReader {
return result return result
} }
// GetByte returns a byte from the stream // ReadByte reads a byte from the stream
func (v *StreamReader) GetByte() byte { func (v *StreamReader) ReadByte() (byte, error) {
if v.position >= v.Size() {
return 0, io.EOF
}
result := v.data[v.position] result := v.data[v.position]
v.position++ v.position++
return result return result, nil
} }
// GetInt16 returns a int16 word from the stream // ReadInt16 returns a int16 word from the stream
func (v *StreamReader) GetInt16() int16 { func (v *StreamReader) ReadInt16() (int16, error) {
return int16(v.GetUInt16()) b, err := v.ReadUInt16()
return int16(b), err
} }
// GetUInt16 returns a uint16 word from the stream // ReadUInt16 returns a uint16 word from the stream
func (v *StreamReader) ReadUInt16() (uint16, error) {
b, err := v.ReadBytes(bytesPerint16)
if err != nil {
return 0, err
}
return uint16(b[0]) | uint16(b[1])<<8, err
}
// ReadInt32 returns an int32 dword from the stream
func (v *StreamReader) ReadInt32() (int32, error) {
b, err := v.ReadUInt32()
return int32(b), err
}
// ReadUInt32 returns a uint32 dword from the stream
//nolint //nolint
func (v *StreamReader) GetUInt16() uint16 { func (v *StreamReader) ReadUInt32() (uint32, error) {
b := v.ReadBytes(2) b, err := v.ReadBytes(bytesPerint32)
return uint16(b[0]) | uint16(b[1])<<8 if err != nil {
return 0, err
}
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24, err
} }
// GetInt32 returns an int32 dword from the stream // ReadInt64 returns a uint64 qword from the stream
func (v *StreamReader) GetInt32() int32 { func (v *StreamReader) ReadInt64() (int64, error) {
return int32(v.GetUInt32()) b, err := v.ReadUInt64()
return int64(b), err
} }
// GetUInt32 returns a uint32 dword from the stream // ReadUInt64 returns a uint64 qword from the stream
//nolint //nolint
func (v *StreamReader) GetUInt32() uint32 { func (v *StreamReader) ReadUInt64() (uint64, error) {
b := v.ReadBytes(4) b, err := v.ReadBytes(bytesPerint64)
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 if err != nil {
} return 0, err
}
// GetInt64 returns a uint64 qword from the stream
func (v *StreamReader) GetInt64() int64 {
return int64(v.GetUInt64())
}
// GetUInt64 returns a uint64 qword from the stream
//nolint
func (v *StreamReader) GetUInt64() uint64 {
b := v.ReadBytes(8)
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, err
} }
// GetPosition returns the current stream position // Position returns the current stream position
func (v *StreamReader) GetPosition() uint64 { func (v *StreamReader) Position() uint64 {
return v.position return v.position
} }
@ -75,22 +100,22 @@ func (v *StreamReader) SetPosition(newPosition uint64) {
v.position = newPosition v.position = newPosition
} }
// GetSize returns the total size of the stream in bytes // Size returns the total size of the stream in bytes
func (v *StreamReader) GetSize() uint64 { func (v *StreamReader) Size() uint64 {
return uint64(len(v.data)) return uint64(len(v.data))
} }
// ReadByte implements io.ByteReader
func (v *StreamReader) ReadByte() (byte, error) {
return v.GetByte(), nil
}
// ReadBytes reads multiple bytes // ReadBytes reads multiple bytes
func (v *StreamReader) ReadBytes(count int) []byte { func (v *StreamReader) ReadBytes(count int) ([]byte, error) {
size := v.Size()
if v.position >= size || v.position+uint64(count) > size {
return nil, io.EOF
}
result := v.data[v.position : v.position+uint64(count)] result := v.data[v.position : v.position+uint64(count)]
v.position += uint64(count) v.position += uint64(count)
return result return result, nil
} }
// SkipBytes moves the stream position forward by the given amount // SkipBytes moves the stream position forward by the given amount
@ -100,10 +125,10 @@ func (v *StreamReader) SkipBytes(count int) {
// Read implements io.Reader // Read implements io.Reader
func (v *StreamReader) Read(p []byte) (n int, err error) { func (v *StreamReader) Read(p []byte) (n int, err error) {
streamLength := v.GetSize() streamLength := v.Size()
for i := 0; ; i++ { for i := 0; ; i++ {
if v.GetPosition() >= streamLength { if v.Position() >= streamLength {
return i, io.EOF return i, io.EOF
} }
@ -111,7 +136,10 @@ func (v *StreamReader) Read(p []byte) (n int, err error) {
return i, nil return i, nil
} }
p[i] = v.GetByte() p[i], err = v.ReadByte()
if err != nil {
return i, err
}
} }
} }

View File

@ -8,22 +8,26 @@ func TestStreamReaderByte(t *testing.T) {
data := []byte{0x78, 0x56, 0x34, 0x12} data := []byte{0x78, 0x56, 0x34, 0x12}
sr := CreateStreamReader(data) sr := CreateStreamReader(data)
if sr.GetPosition() != 0 { if sr.Position() != 0 {
t.Fatal("StreamReader.GetPosition() did not start at 0") t.Fatal("StreamReader.Position() did not start at 0")
} }
if ss := sr.GetSize(); ss != 4 { if ss := sr.Size(); ss != 4 {
t.Fatalf("StreamREader.GetSize() was expected to return %d, but returned %d instead", 4, ss) t.Fatalf("StreamREader.Size() was expected to return %d, but returned %d instead", 4, ss)
} }
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
ret := sr.GetByte() ret, err := sr.ReadByte()
if err != nil {
t.Error(err)
}
if ret != data[i] { if ret != data[i] {
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", data[i], ret) t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", data[i], ret)
} }
if pos := sr.GetPosition(); pos != uint64(i+1) { if pos := sr.Position(); pos != uint64(i+1) {
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", i, pos) t.Fatalf("StreamReader.Position() should be at %d, but was at %d instead", i, pos)
} }
} }
} }
@ -31,36 +35,48 @@ func TestStreamReaderByte(t *testing.T) {
func TestStreamReaderWord(t *testing.T) { func TestStreamReaderWord(t *testing.T) {
data := []byte{0x78, 0x56, 0x34, 0x12} data := []byte{0x78, 0x56, 0x34, 0x12}
sr := CreateStreamReader(data) sr := CreateStreamReader(data)
ret := sr.GetUInt16()
ret, err := sr.ReadUInt16()
if err != nil {
t.Error(err)
}
if ret != 0x5678 { if ret != 0x5678 {
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x5678, ret) t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x5678, ret)
} }
if pos := sr.GetPosition(); pos != 2 { if pos := sr.Position(); pos != 2 {
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 2, pos) t.Fatalf("StreamReader.Position() should be at %d, but was at %d instead", 2, pos)
}
ret, err = sr.ReadUInt16()
if err != nil {
t.Error(err)
} }
ret = sr.GetUInt16()
if ret != 0x1234 { if ret != 0x1234 {
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x1234, ret) t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x1234, ret)
} }
if pos := sr.GetPosition(); pos != 4 { if pos := sr.Position(); pos != 4 {
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos) t.Fatalf("StreamReader.Position() should be at %d, but was at %d instead", 4, pos)
} }
} }
func TestStreamReaderDword(t *testing.T) { func TestStreamReaderDword(t *testing.T) {
data := []byte{0x78, 0x56, 0x34, 0x12} data := []byte{0x78, 0x56, 0x34, 0x12}
sr := CreateStreamReader(data) sr := CreateStreamReader(data)
ret := sr.GetUInt32()
ret, err := sr.ReadUInt32()
if err != nil {
t.Error(err)
}
if ret != 0x12345678 { if ret != 0x12345678 {
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x12345678, ret) t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x12345678, ret)
} }
if pos := sr.GetPosition(); pos != 4 { if pos := sr.Position(); pos != 4 {
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos) t.Fatalf("StreamReader.Position() should be at %d, but was at %d instead", 4, pos)
} }
} }

View File

@ -57,6 +57,7 @@ func (ad *AnimationData) GetRecords(name string) []*AnimationDataRecord {
} }
// Load loads the data into an AnimationData struct // Load loads the data into an AnimationData struct
//nolint:gocognit,funlen // can't reduce
func Load(data []byte) (*AnimationData, error) { func Load(data []byte) (*AnimationData, error) {
reader := d2datautils.CreateStreamReader(data) reader := d2datautils.CreateStreamReader(data)
animdata := &AnimationData{} animdata := &AnimationData{}
@ -65,7 +66,11 @@ func Load(data []byte) (*AnimationData, error) {
animdata.entries = make(map[string][]*AnimationDataRecord) animdata.entries = make(map[string][]*AnimationDataRecord)
for blockIdx := range animdata.blocks { for blockIdx := range animdata.blocks {
recordCount := reader.GetUInt32() recordCount, err := reader.ReadUInt32()
if err != nil {
return nil, err
}
if recordCount > maxRecordsPerBlock { if recordCount > maxRecordsPerBlock {
return nil, fmt.Errorf("more than %d records in block", maxRecordsPerBlock) return nil, fmt.Errorf("more than %d records in block", maxRecordsPerBlock)
} }
@ -73,7 +78,10 @@ func Load(data []byte) (*AnimationData, error) {
records := make([]*AnimationDataRecord, recordCount) records := make([]*AnimationDataRecord, recordCount)
for recordIdx := uint32(0); recordIdx < recordCount; recordIdx++ { for recordIdx := uint32(0); recordIdx < recordCount; recordIdx++ {
nameBytes := reader.ReadBytes(byteCountName) nameBytes, err := reader.ReadBytes(byteCountName)
if err != nil {
return nil, err
}
if nameBytes[byteCountName-1] != byte(0) { if nameBytes[byteCountName-1] != byte(0) {
return nil, errors.New("animdata AnimationDataRecord name missing null terminator byte") return nil, errors.New("animdata AnimationDataRecord name missing null terminator byte")
@ -84,15 +92,27 @@ func Load(data []byte) (*AnimationData, error) {
animdata.hashTable[hashIdx] = hashName(name) animdata.hashTable[hashIdx] = hashName(name)
frames := reader.GetUInt32() frames, err := reader.ReadUInt32()
speed := reader.GetUInt16() if err != nil {
return nil, err
}
speed, err := reader.ReadUInt16()
if err != nil {
return nil, err
}
reader.SkipBytes(byteCountSpeedPadding) reader.SkipBytes(byteCountSpeedPadding)
events := make(map[int]AnimationEvent) events := make(map[int]AnimationEvent)
for eventIdx := 0; eventIdx < numEvents; eventIdx++ { for eventIdx := 0; eventIdx < numEvents; eventIdx++ {
event := AnimationEvent(reader.GetByte()) eventByte, err := reader.ReadByte()
if err != nil {
return nil, err
}
event := AnimationEvent(eventByte)
if event != AnimationEventNone { if event != AnimationEventNone {
events[eventIdx] = event events[eventIdx] = event
} }
@ -122,7 +142,7 @@ func Load(data []byte) (*AnimationData, error) {
animdata.blocks[blockIdx] = b animdata.blocks[blockIdx] = b
} }
if reader.GetPosition() != uint64(len(data)) { if reader.Position() != uint64(len(data)) {
return nil, errors.New("unable to parse animation data") return nil, errors.New("unable to parse animation data")
} }

View File

@ -7,6 +7,32 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
const (
unknownByteCount = 21
numHeaderBytes = 4 + unknownByteCount
numLayerBytes = 9
)
const (
headerNumLayers = iota
headerFramesPerDir
headerNumDirs
headerSpeed = numHeaderBytes - 1
)
const (
layerType = iota
layerShadow
layerSelectable
layerTransparent
layerDrawEffect
layerWeaponClass
)
const (
badCharacter = string(byte(0))
)
// COF is a structure that represents a COF file. // COF is a structure that represents a COF file.
type COF struct { type COF struct {
NumberOfDirections int NumberOfDirections int
@ -23,13 +49,20 @@ type COF struct {
func Load(fileData []byte) (*COF, error) { func Load(fileData []byte) (*COF, error) {
result := &COF{} result := &COF{}
streamReader := d2datautils.CreateStreamReader(fileData) streamReader := d2datautils.CreateStreamReader(fileData)
result.NumberOfLayers = int(streamReader.GetByte())
result.FramesPerDirection = int(streamReader.GetByte())
result.NumberOfDirections = int(streamReader.GetByte())
streamReader.SkipBytes(21) //nolint:gomnd // Unknown data var b []byte
result.Speed = int(streamReader.GetByte()) var err error
b, err = streamReader.ReadBytes(numHeaderBytes)
if err != nil {
return nil, err
}
result.NumberOfLayers = int(b[headerNumLayers])
result.FramesPerDirection = int(b[headerFramesPerDir])
result.NumberOfDirections = int(b[headerNumDirs])
result.Speed = int(b[headerSpeed])
streamReader.SkipBytes(3) //nolint:gomnd // Unknown data streamReader.SkipBytes(3) //nolint:gomnd // Unknown data
@ -38,27 +71,44 @@ func Load(fileData []byte) (*COF, error) {
for i := 0; i < result.NumberOfLayers; i++ { for i := 0; i < result.NumberOfLayers; i++ {
layer := CofLayer{} layer := CofLayer{}
layer.Type = d2enum.CompositeType(streamReader.GetByte())
layer.Shadow = streamReader.GetByte() b, err = streamReader.ReadBytes(numLayerBytes)
layer.Selectable = streamReader.GetByte() != 0 if err != nil {
layer.Transparent = streamReader.GetByte() != 0 return nil, err
layer.DrawEffect = d2enum.DrawEffect(streamReader.GetByte()) }
weaponClassStr := streamReader.ReadBytes(4) //nolint:gomnd // Binary data
layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll(string(weaponClassStr), string(byte(0)), ""))) layer.Type = d2enum.CompositeType(b[layerType])
layer.Shadow = b[layerShadow]
layer.Selectable = b[layerSelectable] > 0
layer.Transparent = b[layerTransparent] > 0
layer.DrawEffect = d2enum.DrawEffect(b[layerDrawEffect])
layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll(
string(b[layerWeaponClass:]), badCharacter, "")))
result.CofLayers[i] = layer result.CofLayers[i] = layer
result.CompositeLayers[layer.Type] = i result.CompositeLayers[layer.Type] = i
} }
animationFrameBytes := streamReader.ReadBytes(result.FramesPerDirection) b, err = streamReader.ReadBytes(result.FramesPerDirection)
if err != nil {
return nil, err
}
result.AnimationFrames = make([]d2enum.AnimationFrame, result.FramesPerDirection) result.AnimationFrames = make([]d2enum.AnimationFrame, result.FramesPerDirection)
for i := range animationFrameBytes { for i := range b {
result.AnimationFrames[i] = d2enum.AnimationFrame(animationFrameBytes[i]) result.AnimationFrames[i] = d2enum.AnimationFrame(b[i])
} }
priorityLen := result.FramesPerDirection * result.NumberOfDirections * result.NumberOfLayers priorityLen := result.FramesPerDirection * result.NumberOfDirections * result.NumberOfLayers
result.Priority = make([][][]d2enum.CompositeType, result.NumberOfDirections) result.Priority = make([][][]d2enum.CompositeType, result.NumberOfDirections)
priorityBytes := streamReader.ReadBytes(priorityLen)
priorityBytes, err := streamReader.ReadBytes(priorityLen)
if err != nil {
return nil, err
}
priorityIndex := 0 priorityIndex := 0
for direction := 0; direction < result.NumberOfDirections; direction++ { for direction := 0; direction < result.NumberOfDirections; direction++ {

View File

@ -7,6 +7,9 @@ import (
const ( const (
endOfScanLine = 0x80 endOfScanLine = 0x80
maxRunLength = 0x7f maxRunLength = 0x7f
terminationSize = 4
terminatorSize = 3
) )
type scanlineState int type scanlineState int
@ -31,49 +34,118 @@ type DC6 struct {
// Load uses restruct to read the binary dc6 data into structs then parses image data from the frame data. // Load uses restruct to read the binary dc6 data into structs then parses image data from the frame data.
func Load(data []byte) (*DC6, error) { func Load(data []byte) (*DC6, error) {
const (
terminationSize = 4
terminatorSize = 3
)
r := d2datautils.CreateStreamReader(data) r := d2datautils.CreateStreamReader(data)
var dc DC6 var dc DC6
dc.Version = r.GetInt32()
dc.Flags = r.GetUInt32() var err error
dc.Encoding = r.GetUInt32()
dc.Termination = r.ReadBytes(terminationSize) err = dc.loadHeader(r)
dc.Directions = r.GetUInt32() if err != nil {
dc.FramesPerDirection = r.GetUInt32() return nil, err
}
frameCount := int(dc.Directions * dc.FramesPerDirection) frameCount := int(dc.Directions * dc.FramesPerDirection)
dc.FramePointers = make([]uint32, frameCount) dc.FramePointers = make([]uint32, frameCount)
for i := 0; i < frameCount; i++ { for i := 0; i < frameCount; i++ {
dc.FramePointers[i] = r.GetUInt32() dc.FramePointers[i], err = r.ReadUInt32()
if err != nil {
return nil, err
}
} }
dc.Frames = make([]*DC6Frame, frameCount) dc.Frames = make([]*DC6Frame, frameCount)
for i := 0; i < frameCount; i++ { if err := dc.loadFrames(r); err != nil {
frame := &DC6Frame{ return nil, err
Flipped: r.GetUInt32(),
Width: r.GetUInt32(),
Height: r.GetUInt32(),
OffsetX: r.GetInt32(),
OffsetY: r.GetInt32(),
Unknown: r.GetUInt32(),
NextBlock: r.GetUInt32(),
Length: r.GetUInt32(),
}
frame.FrameData = r.ReadBytes(int(frame.Length))
frame.Terminator = r.ReadBytes(terminatorSize)
dc.Frames[i] = frame
} }
return &dc, nil return &dc, nil
} }
func (d *DC6) loadHeader(r *d2datautils.StreamReader) error {
var err error
if d.Version, err = r.ReadInt32(); err != nil {
return err
}
if d.Flags, err = r.ReadUInt32(); err != nil {
return err
}
if d.Encoding, err = r.ReadUInt32(); err != nil {
return err
}
if d.Termination, err = r.ReadBytes(terminationSize); err != nil {
return err
}
if d.Directions, err = r.ReadUInt32(); err != nil {
return err
}
if d.FramesPerDirection, err = r.ReadUInt32(); err != nil {
return err
}
return nil
}
func (d *DC6) loadFrames(r *d2datautils.StreamReader) error {
var err error
for i := 0; i < len(d.FramePointers); i++ {
frame := &DC6Frame{}
if frame.Flipped, err = r.ReadUInt32(); err != nil {
return err
}
if frame.Width, err = r.ReadUInt32(); err != nil {
return err
}
if frame.Height, err = r.ReadUInt32(); err != nil {
return err
}
if frame.OffsetX, err = r.ReadInt32(); err != nil {
return err
}
if frame.OffsetY, err = r.ReadInt32(); err != nil {
return err
}
if frame.Unknown, err = r.ReadUInt32(); err != nil {
return err
}
if frame.NextBlock, err = r.ReadUInt32(); err != nil {
return err
}
if frame.Length, err = r.ReadUInt32(); err != nil {
return err
}
if frame.FrameData, err = r.ReadBytes(int(frame.Length)); err != nil {
return err
}
if frame.Terminator, err = r.ReadBytes(terminatorSize); err != nil {
return err
}
d.Frames[i] = frame
}
return nil
}
// DecodeFrame decodes the given frame to an indexed color texture // DecodeFrame decodes the given frame to an indexed color texture
func (d *DC6) DecodeFrame(frameIndex int) []byte { func (d *DC6) DecodeFrame(frameIndex int) []byte {
frame := d.Frames[frameIndex] frame := d.Frames[frameIndex]

View File

@ -8,7 +8,24 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2path" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2path"
) )
const maxActNumber = 5 const (
maxActNumber = 5
subType1 = 1
subType2 = 2
v2 = 2
v3 = 3
v4 = 4
v7 = 7
v8 = 8
v9 = 9
v10 = 10
v12 = 12
v13 = 13
v14 = 14
v15 = 15
v16 = 16
v18 = 18
)
// DS1 represents the "stamp" data that is used to build up maps. // DS1 represents the "stamp" data that is used to build up maps.
type DS1 struct { type DS1 struct {
@ -29,6 +46,7 @@ type DS1 struct {
} }
// LoadDS1 loads the specified DS1 file // LoadDS1 loads the specified DS1 file
//nolint:funlen,gocognit,gocyclo // will refactor later
func LoadDS1(fileData []byte) (*DS1, error) { func LoadDS1(fileData []byte) (*DS1, error) {
ds1 := &DS1{ ds1 := &DS1{
Act: 1, Act: 1,
@ -37,32 +55,67 @@ func LoadDS1(fileData []byte) (*DS1, error) {
NumberOfShadowLayers: 1, NumberOfShadowLayers: 1,
NumberOfSubstitutionLayers: 0, NumberOfSubstitutionLayers: 0,
} }
br := d2datautils.CreateStreamReader(fileData)
ds1.Version = br.GetInt32()
ds1.Width = br.GetInt32() + 1
ds1.Height = br.GetInt32() + 1
if ds1.Version >= 8 { //nolint:gomnd // Version number br := d2datautils.CreateStreamReader(fileData)
ds1.Act = d2math.MinInt32(maxActNumber, br.GetInt32()+1)
var err error
ds1.Version, err = br.ReadInt32()
if err != nil {
return nil, err
} }
if ds1.Version >= 10 { //nolint:gomnd // Version number ds1.Width, err = br.ReadInt32()
ds1.SubstitutionType = br.GetInt32() if err != nil {
return nil, err
}
ds1.Height, err = br.ReadInt32()
if err != nil {
return nil, err
}
ds1.Width++
ds1.Height++
if ds1.Version >= v8 {
ds1.Act, err = br.ReadInt32()
if err != nil {
return nil, err
}
ds1.Act = d2math.MinInt32(maxActNumber, ds1.Act+1)
}
if ds1.Version >= v10 {
ds1.SubstitutionType, err = br.ReadInt32()
if err != nil {
return nil, err
}
if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 { if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 {
ds1.NumberOfSubstitutionLayers = 1 ds1.NumberOfSubstitutionLayers = 1
} }
} }
if ds1.Version >= 3 { //nolint:gomnd // Version number if ds1.Version >= v3 {
// These files reference things that don't exist anymore :-? // These files reference things that don't exist anymore :-?
numberOfFiles := br.GetInt32() numberOfFiles, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
if err != nil {
return nil, err
}
ds1.Files = make([]string, numberOfFiles) ds1.Files = make([]string, numberOfFiles)
for i := 0; i < int(numberOfFiles); i++ { for i := 0; i < int(numberOfFiles); i++ {
ds1.Files[i] = "" ds1.Files[i] = ""
for { for {
ch := br.GetByte() ch, err := br.ReadByte()
if err != nil {
return nil, err
}
if ch == 0 { if ch == 0 {
break break
} }
@ -72,15 +125,22 @@ func LoadDS1(fileData []byte) (*DS1, error) {
} }
} }
if ds1.Version >= 9 && ds1.Version <= 13 { if ds1.Version >= v9 && ds1.Version <= v13 {
// Skipping two dwords because they are "meaningless"? // Skipping two dwords because they are "meaningless"?
br.SkipBytes(8) //nolint:gomnd // We don't know what's here br.SkipBytes(8) //nolint:gomnd // We don't know what's here
} }
if ds1.Version >= 4 { //nolint:gomnd // Version number if ds1.Version >= v4 {
ds1.NumberOfWalls = br.GetInt32() ds1.NumberOfWalls, err = br.ReadInt32()
if ds1.Version >= 16 { //nolint:gomnd // Version number if err != nil {
ds1.NumberOfFloors = br.GetInt32() return nil, err
}
if ds1.Version >= v16 {
ds1.NumberOfFloors, err = br.ReadInt32()
if err != nil {
return nil, err
}
} else { } else {
ds1.NumberOfFloors = 1 ds1.NumberOfFloors = 1
} }
@ -100,62 +160,139 @@ func LoadDS1(fileData []byte) (*DS1, error) {
} }
} }
ds1.loadLayerStreams(br, layerStream) err = ds1.loadLayerStreams(br, layerStream)
ds1.loadObjects(br) if err != nil {
ds1.loadSubstitutions(br) return nil, err
ds1.loadNPCs(br) }
err = ds1.loadObjects(br)
if err != nil {
return nil, err
}
err = ds1.loadSubstitutions(br)
if err != nil {
return nil, err
}
err = ds1.loadNPCs(br)
if err != nil {
return nil, err
}
return ds1, nil return ds1, nil
} }
func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) { func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error {
if ds1.Version >= 2 { //nolint:gomnd // Version number if ds1.Version < v2 {
numberOfObjects := br.GetInt32() ds1.Objects = make([]Object, 0)
} else {
numberOfObjects, err := br.ReadInt32()
if err != nil {
return err
}
ds1.Objects = make([]Object, numberOfObjects) ds1.Objects = make([]Object, numberOfObjects)
for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ { for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ {
newObject := Object{} obj := Object{}
newObject.Type = int(br.GetInt32()) objType, err := br.ReadInt32()
newObject.ID = int(br.GetInt32()) if err != nil {
newObject.X = int(br.GetInt32()) return err
newObject.Y = int(br.GetInt32()) }
newObject.Flags = int(br.GetInt32())
ds1.Objects[objIdx] = newObject objID, err := br.ReadInt32()
if err != nil {
return err
}
objX, err := br.ReadInt32()
if err != nil {
return err
}
objY, err := br.ReadInt32()
if err != nil {
return err
}
objFlags, err := br.ReadInt32()
if err != nil {
return err
}
obj.Type = int(objType)
obj.ID = int(objID)
obj.X = int(objX)
obj.Y = int(objY)
obj.Flags = int(objFlags)
ds1.Objects[objIdx] = obj
} }
} else {
ds1.Objects = make([]Object, 0)
} }
return nil
} }
func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) { func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error {
if ds1.Version >= 12 && (ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2) { var err error
if ds1.Version >= 18 { //nolint:gomnd // Version number
br.GetUInt32()
}
numberOfSubGroups := br.GetInt32() hasSubstitutions := ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2)
ds1.SubstitutionGroups = make([]SubstitutionGroup, numberOfSubGroups)
for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ { if !hasSubstitutions {
newSub := SubstitutionGroup{}
newSub.TileX = br.GetInt32()
newSub.TileY = br.GetInt32()
newSub.WidthInTiles = br.GetInt32()
newSub.HeightInTiles = br.GetInt32()
newSub.Unknown = br.GetInt32()
ds1.SubstitutionGroups[subIdx] = newSub
}
} else {
ds1.SubstitutionGroups = make([]SubstitutionGroup, 0) ds1.SubstitutionGroups = make([]SubstitutionGroup, 0)
return nil
} }
if ds1.Version >= v18 {
_, _ = br.ReadUInt32()
}
numberOfSubGroups, err := br.ReadInt32()
if err != nil {
return err
}
ds1.SubstitutionGroups = make([]SubstitutionGroup, numberOfSubGroups)
for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ {
newSub := SubstitutionGroup{}
newSub.TileX, err = br.ReadInt32()
if err != nil {
return err
}
newSub.TileY, err = br.ReadInt32()
if err != nil {
return err
}
newSub.WidthInTiles, err = br.ReadInt32()
if err != nil {
return err
}
newSub.HeightInTiles, err = br.ReadInt32()
if err != nil {
return err
}
newSub.Unknown, err = br.ReadInt32()
if err != nil {
return err
}
ds1.SubstitutionGroups[subIdx] = newSub
}
return err
} }
func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType { func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType {
var layerStream []d2enum.LayerStreamType var layerStream []d2enum.LayerStreamType
if ds1.Version < 4 { //nolint:gomnd // Version number if ds1.Version < v4 {
layerStream = []d2enum.LayerStreamType{ layerStream = []d2enum.LayerStreamType{
d2enum.LayerStreamWall1, d2enum.LayerStreamWall1,
d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor1,
@ -189,55 +326,100 @@ func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType {
return layerStream return layerStream
} }
func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) { func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
if ds1.Version >= 14 { //nolint:gomnd // Version number var err error
numberOfNpcs := br.GetInt32()
for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ {
numPaths := br.GetInt32()
npcX := int(br.GetInt32())
npcY := int(br.GetInt32())
objIdx := -1
for idx, ds1Obj := range ds1.Objects { if ds1.Version < v14 {
if ds1Obj.X == npcX && ds1Obj.Y == npcY { return err
objIdx = idx }
break
} numberOfNpcs, err := br.ReadInt32()
if err != nil {
return err
}
for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ {
numPaths, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
if err != nil {
return err
}
npcX, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
if err != nil {
return err
}
npcY, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
if err != nil {
return err
}
objIdx := -1
for idx, ds1Obj := range ds1.Objects {
if ds1Obj.X == int(npcX) && ds1Obj.Y == int(npcY) {
objIdx = idx
break
} }
}
if objIdx > -1 { if objIdx > -1 {
ds1.loadNpcPaths(br, objIdx, int(numPaths)) err = ds1.loadNpcPaths(br, objIdx, int(numPaths))
if err != nil {
return err
}
} else {
if ds1.Version >= v15 {
br.SkipBytes(int(numPaths) * 3) //nolint:gomnd // Unknown data
} else { } else {
if ds1.Version >= 15 { //nolint:gomnd // Version number br.SkipBytes(int(numPaths) * 2) //nolint:gomnd // Unknown data
br.SkipBytes(int(numPaths) * 3) //nolint:gomnd // Unknown data
} else {
br.SkipBytes(int(numPaths) * 2) //nolint:gomnd // Unknown data
}
} }
} }
} }
return err
} }
func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) { func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) error {
var err error
if ds1.Objects[objIdx].Paths == nil { if ds1.Objects[objIdx].Paths == nil {
ds1.Objects[objIdx].Paths = make([]d2path.Path, numPaths) ds1.Objects[objIdx].Paths = make([]d2path.Path, numPaths)
} }
for pathIdx := 0; pathIdx < numPaths; pathIdx++ { for pathIdx := 0; pathIdx < numPaths; pathIdx++ {
newPath := d2path.Path{} newPath := d2path.Path{}
newPath.Position = d2vector.NewPosition(
float64(br.GetInt32()),
float64(br.GetInt32()))
if ds1.Version >= 15 { //nolint:gomnd // Version number px, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
newPath.Action = int(br.GetInt32()) if err != nil {
return err
}
py, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
if err != nil {
return err
}
newPath.Position = d2vector.NewPosition(float64(px), float64(py))
if ds1.Version >= v15 {
action, err := br.ReadInt32()
if err != nil {
return err
}
newPath.Action = int(action)
} }
ds1.Objects[objIdx].Paths[pathIdx] = newPath ds1.Objects[objIdx].Paths[pathIdx] = newPath
} }
return err
} }
func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2enum.LayerStreamType) { func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2enum.LayerStreamType) error {
var err error
var dirLookup = []int32{ var dirLookup = []int32{
0x00, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03, 0x05, 0x05, 0x06, 0x00, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03, 0x05, 0x05, 0x06,
0x06, 0x07, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x06, 0x07, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
@ -249,7 +431,10 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
for y := 0; y < int(ds1.Height); y++ { for y := 0; y < int(ds1.Height); y++ {
for x := 0; x < int(ds1.Width); x++ { for x := 0; x < int(ds1.Width); x++ {
dw := br.GetUInt32() dw, err := br.ReadUInt32() //nolint:govet // i want to re-use the err variable...
if err != nil {
return err
}
switch layerStreamType { switch layerStreamType {
case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4:
@ -265,7 +450,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1)
c := int32(dw & 0x000000FF) //nolint:gomnd // Bitmask c := int32(dw & 0x000000FF) //nolint:gomnd // Bitmask
if ds1.Version < 7 { //nolint:gomnd // Version number if ds1.Version < v7 {
if c < int32(len(dirLookup)) { if c < int32(len(dirLookup)) {
c = dirLookup[c] c = dirLookup[c]
} }
@ -294,4 +479,6 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
} }
} }
} }
return err
} }

View File

@ -22,55 +22,146 @@ const (
BlockFormatIsometric BlockDataFormat = 1 BlockFormatIsometric BlockDataFormat = 1
) )
const (
numUnknownHeaderBytes = 260
knownMajorVersion = 7
knownMinorVersion = 6
numUnknownTileBytes1 = 4
numUnknownTileBytes2 = 4
numUnknownTileBytes3 = 7
numUnknownTileBytes4 = 12
)
// LoadDT1 loads a DT1 record // LoadDT1 loads a DT1 record
//nolint:funlen // Can't reduce //nolint:funlen,gocognit,gocyclo // Can't reduce
func LoadDT1(fileData []byte) (*DT1, error) { func LoadDT1(fileData []byte) (*DT1, error) {
result := &DT1{} result := &DT1{}
br := d2datautils.CreateStreamReader(fileData) br := d2datautils.CreateStreamReader(fileData)
ver1 := br.GetInt32()
ver2 := br.GetInt32()
if ver1 != 7 || ver2 != 6 { var err error
return nil, fmt.Errorf("expected to have a version of 7.6, but got %d.%d instead", ver1, ver2)
majorVersion, err := br.ReadInt32()
if err != nil {
return nil, err
} }
br.SkipBytes(260) //nolint:gomnd // Unknown data minorVersion, err := br.ReadInt32()
if err != nil {
return nil, err
}
numberOfTiles := br.GetInt32() if majorVersion != knownMajorVersion || minorVersion != knownMinorVersion {
br.SetPosition(uint64(br.GetInt32())) const fmtErr = "expected to have a version of 7.6, but got %d.%d instead"
return nil, fmt.Errorf(fmtErr, majorVersion, minorVersion)
}
br.SkipBytes(numUnknownHeaderBytes)
numberOfTiles, err := br.ReadInt32()
if err != nil {
return nil, err
}
position, err := br.ReadInt32()
if err != nil {
return nil, err
}
br.SetPosition(uint64(position))
result.Tiles = make([]Tile, numberOfTiles) result.Tiles = make([]Tile, numberOfTiles)
for tileIdx := range result.Tiles { for tileIdx := range result.Tiles {
newTile := Tile{} tile := Tile{}
newTile.Direction = br.GetInt32()
newTile.RoofHeight = br.GetInt16()
newTile.MaterialFlags = NewMaterialFlags(br.GetUInt16())
newTile.Height = br.GetInt32()
newTile.Width = br.GetInt32()
br.SkipBytes(4) //nolint:gomnd // Unknown data tile.Direction, err = br.ReadInt32()
if err != nil {
newTile.Type = br.GetInt32() return nil, err
newTile.Style = br.GetInt32()
newTile.Sequence = br.GetInt32()
newTile.RarityFrameIndex = br.GetInt32()
br.SkipBytes(4) //nolint:gomnd // Unknown data
for i := range newTile.SubTileFlags {
newTile.SubTileFlags[i] = NewSubTileFlags(br.GetByte())
} }
br.SkipBytes(7) //nolint:gomnd // Unknown data tile.RoofHeight, err = br.ReadInt16()
if err != nil {
return nil, err
}
newTile.blockHeaderPointer = br.GetInt32() var matFlagBytes uint16
newTile.blockHeaderSize = br.GetInt32()
newTile.Blocks = make([]Block, br.GetInt32())
br.SkipBytes(12) //nolint:gomnd // Unknown data matFlagBytes, err = br.ReadUInt16()
if err != nil {
return nil, err
}
result.Tiles[tileIdx] = newTile tile.MaterialFlags = NewMaterialFlags(matFlagBytes)
tile.Height, err = br.ReadInt32()
if err != nil {
return nil, err
}
tile.Width, err = br.ReadInt32()
if err != nil {
return nil, err
}
br.SkipBytes(numUnknownTileBytes1)
tile.Type, err = br.ReadInt32()
if err != nil {
return nil, err
}
tile.Style, err = br.ReadInt32()
if err != nil {
return nil, err
}
tile.Sequence, err = br.ReadInt32()
if err != nil {
return nil, err
}
tile.RarityFrameIndex, err = br.ReadInt32()
if err != nil {
return nil, err
}
br.SkipBytes(numUnknownTileBytes2)
for i := range tile.SubTileFlags {
var subtileFlagBytes byte
subtileFlagBytes, err = br.ReadByte()
if err != nil {
return nil, err
}
tile.SubTileFlags[i] = NewSubTileFlags(subtileFlagBytes)
}
br.SkipBytes(numUnknownTileBytes3)
tile.blockHeaderPointer, err = br.ReadInt32()
if err != nil {
return nil, err
}
tile.blockHeaderSize, err = br.ReadInt32()
if err != nil {
return nil, err
}
var numBlocks int32
numBlocks, err = br.ReadInt32()
if err != nil {
return nil, err
}
tile.Blocks = make([]Block, numBlocks)
br.SkipBytes(numUnknownTileBytes4)
result.Tiles[tileIdx] = tile
} }
for tileIdx := range result.Tiles { for tileIdx := range result.Tiles {
@ -78,14 +169,32 @@ func LoadDT1(fileData []byte) (*DT1, error) {
br.SetPosition(uint64(tile.blockHeaderPointer)) br.SetPosition(uint64(tile.blockHeaderPointer))
for blockIdx := range tile.Blocks { for blockIdx := range tile.Blocks {
result.Tiles[tileIdx].Blocks[blockIdx].X = br.GetInt16() result.Tiles[tileIdx].Blocks[blockIdx].X, err = br.ReadInt16()
result.Tiles[tileIdx].Blocks[blockIdx].Y = br.GetInt16() if err != nil {
return nil, err
}
result.Tiles[tileIdx].Blocks[blockIdx].Y, err = br.ReadInt16()
if err != nil {
return nil, err
}
br.SkipBytes(2) //nolint:gomnd // Unknown data br.SkipBytes(2) //nolint:gomnd // Unknown data
result.Tiles[tileIdx].Blocks[blockIdx].GridX = br.GetByte() result.Tiles[tileIdx].Blocks[blockIdx].GridX, err = br.ReadByte()
result.Tiles[tileIdx].Blocks[blockIdx].GridY = br.GetByte() if err != nil {
formatValue := br.GetInt16() return nil, err
}
result.Tiles[tileIdx].Blocks[blockIdx].GridY, err = br.ReadByte()
if err != nil {
return nil, err
}
formatValue, err := br.ReadInt16()
if err != nil {
return nil, err
}
if formatValue == 1 { if formatValue == 1 {
result.Tiles[tileIdx].Blocks[blockIdx].Format = BlockFormatIsometric result.Tiles[tileIdx].Blocks[blockIdx].Format = BlockFormatIsometric
@ -93,16 +202,27 @@ func LoadDT1(fileData []byte) (*DT1, error) {
result.Tiles[tileIdx].Blocks[blockIdx].Format = BlockFormatRLE result.Tiles[tileIdx].Blocks[blockIdx].Format = BlockFormatRLE
} }
result.Tiles[tileIdx].Blocks[blockIdx].Length = br.GetInt32() result.Tiles[tileIdx].Blocks[blockIdx].Length, err = br.ReadInt32()
if err != nil {
return nil, err
}
br.SkipBytes(2) //nolint:gomnd // Unknown data br.SkipBytes(2) //nolint:gomnd // Unknown data
result.Tiles[tileIdx].Blocks[blockIdx].FileOffset = br.GetInt32() result.Tiles[tileIdx].Blocks[blockIdx].FileOffset, err = br.ReadInt32()
if err != nil {
return nil, err
}
} }
for blockIndex, block := range tile.Blocks { for blockIndex, block := range tile.Blocks {
br.SetPosition(uint64(tile.blockHeaderPointer + block.FileOffset)) br.SetPosition(uint64(tile.blockHeaderPointer + block.FileOffset))
encodedData := br.ReadBytes(int(block.Length))
encodedData, err := br.ReadBytes(int(block.Length))
if err != nil {
return nil, err
}
tile.Blocks[blockIndex].EncodedData = encodedData tile.Blocks[blockIndex].EncodedData = encodedData
} }
} }

View File

@ -223,7 +223,7 @@ func (v *Stream) loadBlock(blockIndex, expectedLength uint32) ([]byte, error) {
return data, nil return data, nil
} }
//nolint:gomnd // Will fix enum values later //nolint:gomnd,funlen,gocyclo // Will fix enum values later, can't help function length
func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) { func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) {
compressionType := data[0] compressionType := data[0]
@ -237,9 +237,9 @@ func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) {
case 0x10: // BZip2 case 0x10: // BZip2
return []byte{}, errors.New("bzip2 decompression not supported") return []byte{}, errors.New("bzip2 decompression not supported")
case 0x80: // IMA ADPCM Stereo case 0x80: // IMA ADPCM Stereo
return d2compression.WavDecompress(data[1:], 2), nil return d2compression.WavDecompress(data[1:], 2)
case 0x40: // IMA ADPCM Mono case 0x40: // IMA ADPCM Mono
return d2compression.WavDecompress(data[1:], 1), nil return d2compression.WavDecompress(data[1:], 1)
case 0x12: case 0x12:
return []byte{}, errors.New("lzma decompression not supported") return []byte{}, errors.New("lzma decompression not supported")
// Combos // Combos
@ -250,8 +250,11 @@ func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) {
// sparse then bzip2 // sparse then bzip2
return []byte{}, errors.New("sparse decompression + bzip2 decompression not supported") return []byte{}, errors.New("sparse decompression + bzip2 decompression not supported")
case 0x41: case 0x41:
sinput := d2compression.HuffmanDecompress(data[1:]) sinput, err := d2compression.WavDecompress(d2compression.HuffmanDecompress(data[1:]), 1)
sinput = d2compression.WavDecompress(sinput, 1) if err != nil {
return nil, err
}
tmp := make([]byte, len(sinput)) tmp := make([]byte, len(sinput))
copy(tmp, sinput) copy(tmp, sinput)
@ -262,8 +265,11 @@ func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) {
// return MpqWavCompression.Decompress(new MemoryStream(result), 1); // return MpqWavCompression.Decompress(new MemoryStream(result), 1);
return []byte{}, errors.New("pk + mpqwav decompression not supported") return []byte{}, errors.New("pk + mpqwav decompression not supported")
case 0x81: case 0x81:
sinput := d2compression.HuffmanDecompress(data[1:]) sinput, err := d2compression.WavDecompress(d2compression.HuffmanDecompress(data[1:]), 2)
sinput = d2compression.WavDecompress(sinput, 2) if err != nil {
return nil, err
}
tmp := make([]byte, len(sinput)) tmp := make([]byte, len(sinput))
copy(tmp, sinput) copy(tmp, sinput)

View File

@ -10,6 +10,97 @@ import (
// TextDictionary is a string map // TextDictionary is a string map
type TextDictionary map[string]string type TextDictionary map[string]string
func (td TextDictionary) loadHashEntries(hashEntries []*textDictionaryHashEntry, br *d2datautils.StreamReader) error {
for i := 0; i < len(hashEntries); i++ {
entry := textDictionaryHashEntry{}
active, err := br.ReadByte()
if err != nil {
return err
}
entry.IsActive = active > 0
entry.Index, err = br.ReadUInt16()
if err != nil {
return err
}
entry.HashValue, err = br.ReadUInt32()
if err != nil {
return err
}
entry.IndexString, err = br.ReadUInt32()
if err != nil {
return err
}
entry.NameString, err = br.ReadUInt32()
if err != nil {
return err
}
entry.NameLength, err = br.ReadUInt16()
if err != nil {
return err
}
hashEntries[i] = &entry
}
for idx := range hashEntries {
if !hashEntries[idx].IsActive {
continue
}
if err := td.loadHashEntry(idx, hashEntries[idx], br); err != nil {
return err
}
}
return nil
}
func (td TextDictionary) loadHashEntry(idx int, hashEntry *textDictionaryHashEntry, br *d2datautils.StreamReader) error {
br.SetPosition(uint64(hashEntry.NameString))
nameVal, err := br.ReadBytes(int(hashEntry.NameLength - 1))
if err != nil {
return err
}
value := string(nameVal)
br.SetPosition(uint64(hashEntry.IndexString))
key := ""
for {
b, err := br.ReadByte()
if b == 0 {
break
}
if err != nil {
return err
}
key += string(b)
}
if key == "x" || key == "X" {
key = "#" + strconv.Itoa(idx)
}
_, exists := td[key]
if !exists {
td[key] = value
}
return nil
}
type textDictionaryHashEntry struct { type textDictionaryHashEntry struct {
IsActive bool IsActive bool
Index uint16 Index uint16
@ -30,71 +121,46 @@ func LoadTextDictionary(dictionaryData []byte) (TextDictionary, error) {
br := d2datautils.CreateStreamReader(dictionaryData) br := d2datautils.CreateStreamReader(dictionaryData)
// skip past the CRC // skip past the CRC
br.ReadBytes(crcByteCount) _, _ = br.ReadBytes(crcByteCount)
numberOfElements := br.GetUInt16() var err error
hashTableSize := br.GetUInt32()
numberOfElements, err := br.ReadUInt16()
if err != nil {
return nil, err
}
hashTableSize, err := br.ReadUInt32()
if err != nil {
return nil, err
}
// Version (always 0) // Version (always 0)
if _, err := br.ReadByte(); err != nil { if _, err = br.ReadByte(); err != nil {
return nil, errors.New("error reading Version record") return nil, errors.New("error reading Version record")
} }
br.GetUInt32() // StringOffset _, _ = br.ReadUInt32() // StringOffset
br.GetUInt32() // When the number of times you have missed a match with a hash key equals this value, you give up because it is not there.
br.GetUInt32() // FileSize // When the number of times you have missed a match with a
// hash key equals this value, you give up because it is not there.
_, _ = br.ReadUInt32()
_, _ = br.ReadUInt32() // FileSize
elementIndex := make([]uint16, numberOfElements) elementIndex := make([]uint16, numberOfElements)
for i := 0; i < int(numberOfElements); i++ { for i := 0; i < int(numberOfElements); i++ {
elementIndex[i] = br.GetUInt16() elementIndex[i], err = br.ReadUInt16()
} if err != nil {
return nil, err
hashEntries := make([]textDictionaryHashEntry, hashTableSize)
for i := 0; i < int(hashTableSize); i++ {
hashEntries[i] = textDictionaryHashEntry{
br.GetByte() == 1,
br.GetUInt16(),
br.GetUInt32(),
br.GetUInt32(),
br.GetUInt32(),
br.GetUInt16(),
} }
} }
for idx, hashEntry := range hashEntries { hashEntries := make([]*textDictionaryHashEntry, hashTableSize)
if br.EOF() {
return nil, errors.New("unexpected end of text dictionary file")
}
if !hashEntry.IsActive { err = lookupTable.loadHashEntries(hashEntries, br)
continue if err != nil {
} return nil, err
br.SetPosition(uint64(hashEntry.NameString))
nameVal := br.ReadBytes(int(hashEntry.NameLength - 1))
value := string(nameVal)
br.SetPosition(uint64(hashEntry.IndexString))
key := ""
for {
b := br.GetByte()
if b == 0 {
break
}
key += string(b)
}
if key == "x" || key == "X" {
key = "#" + strconv.Itoa(idx)
}
_, exists := lookupTable[key]
if !exists {
lookupTable[key] = value
}
} }
return lookupTable, nil return lookupTable, nil

View File

@ -29,5 +29,8 @@ func (v *BlizzardIntro) OnLoad(loading d2screen.LoadingState) {
loading.Progress(fiftyPercent) loading.Progress(fiftyPercent)
v.videoDecoder = d2video.CreateBinkDecoder(videoBytes) v.videoDecoder, err = d2video.CreateBinkDecoder(videoBytes)
if err != nil {
loading.Error(err)
}
} }

View File

@ -176,7 +176,11 @@ func (v *Cinematics) playVideo(path string) {
return return
} }
v.videoDecoder = d2video.CreateBinkDecoder(videoBytes) v.videoDecoder, err = d2video.CreateBinkDecoder(videoBytes)
if err != nil {
v.Error(err.Error())
return
}
} }
// Render renders the credits screen // Render renders the credits screen