diff --git a/d2app/initialization.go b/d2app/initialization.go index dd84021f..0828cfc0 100644 --- a/d2app/initialization.go +++ b/d2app/initialization.go @@ -150,7 +150,10 @@ func (a *App) initAnimationData(path string) error { 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)) diff --git a/d2common/d2data/animation_data.go b/d2common/d2data/animation_data.go index 3a29be74..2dc73bc2 100644 --- a/d2common/d2data/animation_data.go +++ b/d2common/d2data/animation_data.go @@ -27,20 +27,48 @@ type AnimationDataRecord struct { type AnimationData map[string][]*AnimationDataRecord // 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) streamReader := d2datautils.CreateStreamReader(rawData) 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++ { - 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{ COFName: strings.ReplaceAll(string(cofNameBytes), string(byte(0)), ""), - FramesPerDirection: int(streamReader.GetInt32()), - AnimationSpeed: int(streamReader.GetInt32()), + FramesPerDirection: int(fpd), + 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) if _, found := animdata[cofIndex]; !found { @@ -51,5 +79,5 @@ func LoadAnimationData(rawData []byte) AnimationData { } } - return animdata + return animdata, nil } diff --git a/d2common/d2data/d2compression/wav.go b/d2common/d2data/d2compression/wav.go index fa02f7e4..f7eed22a 100644 --- a/d2common/d2data/d2compression/wav.go +++ b/d2common/d2data/d2compression/wav.go @@ -6,7 +6,7 @@ import ( // WavDecompress decompresses wav files //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} Array2 := make([]int, channelCount) @@ -35,20 +35,33 @@ func WavDecompress(data []byte, channelCount int) []byte { //nolint:funlen,gocog input := d2datautils.CreateStreamReader(data) 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++ { - temp := input.GetInt16() + temp, err := input.ReadInt16() + if err != nil { + return nil, err + } + Array2[i] = int(temp) output.PushInt16(temp) } channel := channelCount - 1 - for input.GetPosition() < input.GetSize() { - value := input.GetByte() + for input.Position() < input.Size() { + value, err := input.ReadByte() + if err != nil { + return nil, err + } if channelCount == 2 { channel = 1 - channel @@ -129,5 +142,5 @@ func WavDecompress(data []byte, channelCount int) []byte { //nolint:funlen,gocog } } - return output.GetBytes() + return output.GetBytes(), nil } diff --git a/d2common/d2data/d2video/binkdecoder.go b/d2common/d2data/d2video/binkdecoder.go index 8d454b15..d837e9f2 100644 --- a/d2common/d2data/d2video/binkdecoder.go +++ b/d2common/d2data/d2video/binkdecoder.go @@ -1,6 +1,7 @@ package d2video import ( + "errors" "log" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" @@ -29,6 +30,12 @@ const ( BinkVideoModeWidthAndHeightInterlaced ) +const ( + numHeaderBytes = 3 + bikHeaderStr = "BIK" + numAudioTrackUnknownBytes = 2 +) + // BinkAudioAlgorithm represents the type of bink audio algorithm type BinkAudioAlgorithm uint32 @@ -72,75 +79,157 @@ type BinkDecoder struct { } // CreateBinkDecoder returns a new instance of the bink decoder -func CreateBinkDecoder(source []byte) *BinkDecoder { +func CreateBinkDecoder(source []byte) (*BinkDecoder, error) { result := &BinkDecoder{ streamReader: d2datautils.CreateStreamReader(source), } - result.loadHeaderInformation() + err := result.loadHeaderInformation() - return result + return result, err } // GetNextFrame gets the next frame -func (v *BinkDecoder) GetNextFrame() { +func (v *BinkDecoder) GetNextFrame() error { //nolint:gocritic // v.streamReader.SetPosition(uint64(v.FrameIndexTable[i] & 0xFFFFFFFE)) - lengthOfAudioPackets := v.streamReader.GetUInt32() - 4 //nolint:gomnd // decode magic - samplesInPacket := v.streamReader.GetUInt32() + lengthOfAudioPackets, err := v.streamReader.ReadUInt32() + 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) v.frameIndex++ + + return nil } -//nolint:gomnd // Decoder magic -func (v *BinkDecoder) loadHeaderInformation() { +//nolint:gomnd,funlen,gocyclo // Decoder magic, can't help the long function length for now +func (v *BinkDecoder) loadHeaderInformation() error { v.streamReader.SetPosition(0) - headerBytes := v.streamReader.ReadBytes(3) - if string(headerBytes) != "BIK" { - log.Fatal("Invalid header for bink video") + var err error + + 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.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.HasAlphaPlane = ((videoFlags >> 20) & 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) for i := 0; i < int(numberOfAudioTracks); i++ { - v.streamReader.SkipBytes(2) // Unknown - v.AudioTracks[i].AudioChannels = v.streamReader.GetUInt16() + v.streamReader.SkipBytes(numAudioTrackUnknownBytes) + + v.AudioTracks[i].AudioChannels, err = v.streamReader.ReadUInt16() + if err != nil { + return err + } } for i := 0; i < int(numberOfAudioTracks); i++ { - v.AudioTracks[i].AudioSampleRateHz = v.streamReader.GetUInt16() - flags := v.streamReader.GetUInt16() + v.AudioTracks[i].AudioSampleRateHz, err = v.streamReader.ReadUInt16() + 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].Algorithm = BinkAudioAlgorithm((flags >> 12) & 0x1) } 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) 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 } diff --git a/d2common/d2datautils/stream_reader.go b/d2common/d2datautils/stream_reader.go index 31e06c96..95eb61d8 100644 --- a/d2common/d2datautils/stream_reader.go +++ b/d2common/d2datautils/stream_reader.go @@ -4,6 +4,12 @@ import ( "io" ) +const ( + bytesPerint16 = 2 + bytesPerint32 = 4 + bytesPerint64 = 8 +) + // StreamReader allows you to read data from a byte array in various formats type StreamReader struct { data []byte @@ -20,53 +26,72 @@ func CreateStreamReader(source []byte) *StreamReader { return result } -// GetByte returns a byte from the stream -func (v *StreamReader) GetByte() byte { +// ReadByte reads a byte from the stream +func (v *StreamReader) ReadByte() (byte, error) { + if v.position >= v.Size() { + return 0, io.EOF + } + result := v.data[v.position] v.position++ - return result + return result, nil } -// GetInt16 returns a int16 word from the stream -func (v *StreamReader) GetInt16() int16 { - return int16(v.GetUInt16()) +// ReadInt16 returns a int16 word from the stream +func (v *StreamReader) ReadInt16() (int16, error) { + 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 -func (v *StreamReader) GetUInt16() uint16 { - b := v.ReadBytes(2) - return uint16(b[0]) | uint16(b[1])<<8 +func (v *StreamReader) ReadUInt32() (uint32, error) { + b, err := v.ReadBytes(bytesPerint32) + 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 -func (v *StreamReader) GetInt32() int32 { - return int32(v.GetUInt32()) +// ReadInt64 returns a uint64 qword from the stream +func (v *StreamReader) ReadInt64() (int64, error) { + 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 -func (v *StreamReader) GetUInt32() uint32 { - b := v.ReadBytes(4) - return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 -} +func (v *StreamReader) ReadUInt64() (uint64, error) { + b, err := v.ReadBytes(bytesPerint64) + 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 | - 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 -func (v *StreamReader) GetPosition() uint64 { +// Position returns the current stream position +func (v *StreamReader) Position() uint64 { return v.position } @@ -75,22 +100,22 @@ func (v *StreamReader) SetPosition(newPosition uint64) { v.position = newPosition } -// GetSize returns the total size of the stream in bytes -func (v *StreamReader) GetSize() uint64 { +// Size returns the total size of the stream in bytes +func (v *StreamReader) Size() uint64 { return uint64(len(v.data)) } -// ReadByte implements io.ByteReader -func (v *StreamReader) ReadByte() (byte, error) { - return v.GetByte(), nil -} - // 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)] v.position += uint64(count) - return result + return result, nil } // SkipBytes moves the stream position forward by the given amount @@ -100,10 +125,10 @@ func (v *StreamReader) SkipBytes(count int) { // Read implements io.Reader func (v *StreamReader) Read(p []byte) (n int, err error) { - streamLength := v.GetSize() + streamLength := v.Size() for i := 0; ; i++ { - if v.GetPosition() >= streamLength { + if v.Position() >= streamLength { return i, io.EOF } @@ -111,7 +136,10 @@ func (v *StreamReader) Read(p []byte) (n int, err error) { return i, nil } - p[i] = v.GetByte() + p[i], err = v.ReadByte() + if err != nil { + return i, err + } } } diff --git a/d2common/d2datautils/stream_reader_test.go b/d2common/d2datautils/stream_reader_test.go index 94789ec1..4cabdbcf 100644 --- a/d2common/d2datautils/stream_reader_test.go +++ b/d2common/d2datautils/stream_reader_test.go @@ -8,22 +8,26 @@ func TestStreamReaderByte(t *testing.T) { data := []byte{0x78, 0x56, 0x34, 0x12} sr := CreateStreamReader(data) - if sr.GetPosition() != 0 { - t.Fatal("StreamReader.GetPosition() did not start at 0") + if sr.Position() != 0 { + t.Fatal("StreamReader.Position() did not start at 0") } - if ss := sr.GetSize(); ss != 4 { - t.Fatalf("StreamREader.GetSize() was expected to return %d, but returned %d instead", 4, ss) + if ss := sr.Size(); ss != 4 { + t.Fatalf("StreamREader.Size() was expected to return %d, but returned %d instead", 4, ss) } for i := 0; i < len(data); i++ { - ret := sr.GetByte() + ret, err := sr.ReadByte() + if err != nil { + t.Error(err) + } + if ret != data[i] { t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", data[i], ret) } - if pos := sr.GetPosition(); pos != uint64(i+1) { - t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", i, pos) + if pos := sr.Position(); pos != uint64(i+1) { + 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) { data := []byte{0x78, 0x56, 0x34, 0x12} sr := CreateStreamReader(data) - ret := sr.GetUInt16() + + ret, err := sr.ReadUInt16() + if err != nil { + t.Error(err) + } if ret != 0x5678 { t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x5678, ret) } - if pos := sr.GetPosition(); pos != 2 { - t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 2, pos) + if pos := sr.Position(); pos != 2 { + 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 { t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x1234, ret) } - if pos := sr.GetPosition(); pos != 4 { - t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos) + if pos := sr.Position(); pos != 4 { + t.Fatalf("StreamReader.Position() should be at %d, but was at %d instead", 4, pos) } } func TestStreamReaderDword(t *testing.T) { data := []byte{0x78, 0x56, 0x34, 0x12} sr := CreateStreamReader(data) - ret := sr.GetUInt32() + + ret, err := sr.ReadUInt32() + if err != nil { + t.Error(err) + } if ret != 0x12345678 { t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x12345678, ret) } - if pos := sr.GetPosition(); pos != 4 { - t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos) + if pos := sr.Position(); pos != 4 { + t.Fatalf("StreamReader.Position() should be at %d, but was at %d instead", 4, pos) } } diff --git a/d2common/d2fileformats/d2animdata/animdata.go b/d2common/d2fileformats/d2animdata/animdata.go index dd3f65c0..65b23859 100644 --- a/d2common/d2fileformats/d2animdata/animdata.go +++ b/d2common/d2fileformats/d2animdata/animdata.go @@ -57,6 +57,7 @@ func (ad *AnimationData) GetRecords(name string) []*AnimationDataRecord { } // Load loads the data into an AnimationData struct +//nolint:gocognit,funlen // can't reduce func Load(data []byte) (*AnimationData, error) { reader := d2datautils.CreateStreamReader(data) animdata := &AnimationData{} @@ -65,7 +66,11 @@ func Load(data []byte) (*AnimationData, error) { animdata.entries = make(map[string][]*AnimationDataRecord) for blockIdx := range animdata.blocks { - recordCount := reader.GetUInt32() + recordCount, err := reader.ReadUInt32() + if err != nil { + return nil, err + } + if recordCount > 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) 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) { 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) - frames := reader.GetUInt32() - speed := reader.GetUInt16() + frames, err := reader.ReadUInt32() + if err != nil { + return nil, err + } + + speed, err := reader.ReadUInt16() + if err != nil { + return nil, err + } reader.SkipBytes(byteCountSpeedPadding) events := make(map[int]AnimationEvent) 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 { events[eventIdx] = event } @@ -122,7 +142,7 @@ func Load(data []byte) (*AnimationData, error) { 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") } diff --git a/d2common/d2fileformats/d2cof/cof.go b/d2common/d2fileformats/d2cof/cof.go index 1da09f31..685a2cd3 100644 --- a/d2common/d2fileformats/d2cof/cof.go +++ b/d2common/d2fileformats/d2cof/cof.go @@ -7,6 +7,32 @@ import ( "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. type COF struct { NumberOfDirections int @@ -23,13 +49,20 @@ type COF struct { func Load(fileData []byte) (*COF, error) { result := &COF{} 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 @@ -38,27 +71,44 @@ func Load(fileData []byte) (*COF, error) { for i := 0; i < result.NumberOfLayers; i++ { layer := CofLayer{} - layer.Type = d2enum.CompositeType(streamReader.GetByte()) - layer.Shadow = streamReader.GetByte() - layer.Selectable = streamReader.GetByte() != 0 - layer.Transparent = streamReader.GetByte() != 0 - 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)), ""))) + + b, err = streamReader.ReadBytes(numLayerBytes) + if err != nil { + return nil, err + } + + 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.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) - for i := range animationFrameBytes { - result.AnimationFrames[i] = d2enum.AnimationFrame(animationFrameBytes[i]) + for i := range b { + result.AnimationFrames[i] = d2enum.AnimationFrame(b[i]) } priorityLen := result.FramesPerDirection * result.NumberOfDirections * result.NumberOfLayers result.Priority = make([][][]d2enum.CompositeType, result.NumberOfDirections) - priorityBytes := streamReader.ReadBytes(priorityLen) + + priorityBytes, err := streamReader.ReadBytes(priorityLen) + if err != nil { + return nil, err + } + priorityIndex := 0 for direction := 0; direction < result.NumberOfDirections; direction++ { diff --git a/d2common/d2fileformats/d2dc6/dc6.go b/d2common/d2fileformats/d2dc6/dc6.go index f5a04749..0ea28329 100644 --- a/d2common/d2fileformats/d2dc6/dc6.go +++ b/d2common/d2fileformats/d2dc6/dc6.go @@ -7,6 +7,9 @@ import ( const ( endOfScanLine = 0x80 maxRunLength = 0x7f + + terminationSize = 4 + terminatorSize = 3 ) 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. func Load(data []byte) (*DC6, error) { - const ( - terminationSize = 4 - terminatorSize = 3 - ) - r := d2datautils.CreateStreamReader(data) var dc DC6 - dc.Version = r.GetInt32() - dc.Flags = r.GetUInt32() - dc.Encoding = r.GetUInt32() - dc.Termination = r.ReadBytes(terminationSize) - dc.Directions = r.GetUInt32() - dc.FramesPerDirection = r.GetUInt32() + + var err error + + err = dc.loadHeader(r) + if err != nil { + return nil, err + } frameCount := int(dc.Directions * dc.FramesPerDirection) dc.FramePointers = make([]uint32, frameCount) 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) - for i := 0; i < frameCount; i++ { - frame := &DC6Frame{ - 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 + if err := dc.loadFrames(r); err != nil { + return nil, err } 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 func (d *DC6) DecodeFrame(frameIndex int) []byte { frame := d.Frames[frameIndex] diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index f40a2a07..e1154070 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -8,7 +8,24 @@ import ( "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. type DS1 struct { @@ -29,6 +46,7 @@ type DS1 struct { } // LoadDS1 loads the specified DS1 file +//nolint:funlen,gocognit,gocyclo // will refactor later func LoadDS1(fileData []byte) (*DS1, error) { ds1 := &DS1{ Act: 1, @@ -37,32 +55,67 @@ func LoadDS1(fileData []byte) (*DS1, error) { NumberOfShadowLayers: 1, 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 - ds1.Act = d2math.MinInt32(maxActNumber, br.GetInt32()+1) + br := d2datautils.CreateStreamReader(fileData) + + var err error + + ds1.Version, err = br.ReadInt32() + if err != nil { + return nil, err } - if ds1.Version >= 10 { //nolint:gomnd // Version number - ds1.SubstitutionType = br.GetInt32() + ds1.Width, err = br.ReadInt32() + 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 { ds1.NumberOfSubstitutionLayers = 1 } } - if ds1.Version >= 3 { //nolint:gomnd // Version number + if ds1.Version >= v3 { // 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) for i := 0; i < int(numberOfFiles); i++ { ds1.Files[i] = "" for { - ch := br.GetByte() + ch, err := br.ReadByte() + if err != nil { + return nil, err + } + if ch == 0 { 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"? br.SkipBytes(8) //nolint:gomnd // We don't know what's here } - if ds1.Version >= 4 { //nolint:gomnd // Version number - ds1.NumberOfWalls = br.GetInt32() - if ds1.Version >= 16 { //nolint:gomnd // Version number - ds1.NumberOfFloors = br.GetInt32() + if ds1.Version >= v4 { + ds1.NumberOfWalls, err = br.ReadInt32() + if err != nil { + return nil, err + } + + if ds1.Version >= v16 { + ds1.NumberOfFloors, err = br.ReadInt32() + if err != nil { + return nil, err + } } else { ds1.NumberOfFloors = 1 } @@ -100,62 +160,139 @@ func LoadDS1(fileData []byte) (*DS1, error) { } } - ds1.loadLayerStreams(br, layerStream) - ds1.loadObjects(br) - ds1.loadSubstitutions(br) - ds1.loadNPCs(br) + err = ds1.loadLayerStreams(br, layerStream) + if err != nil { + return nil, err + } + + 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 } -func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) { - if ds1.Version >= 2 { //nolint:gomnd // Version number - numberOfObjects := br.GetInt32() +func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error { + if ds1.Version < v2 { + ds1.Objects = make([]Object, 0) + } else { + numberOfObjects, err := br.ReadInt32() + if err != nil { + return err + } + ds1.Objects = make([]Object, numberOfObjects) for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ { - newObject := Object{} - newObject.Type = int(br.GetInt32()) - newObject.ID = int(br.GetInt32()) - newObject.X = int(br.GetInt32()) - newObject.Y = int(br.GetInt32()) - newObject.Flags = int(br.GetInt32()) + obj := Object{} + objType, err := br.ReadInt32() + if err != nil { + return err + } - 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) { - if ds1.Version >= 12 && (ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2) { - if ds1.Version >= 18 { //nolint:gomnd // Version number - br.GetUInt32() - } +func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { + var err error - numberOfSubGroups := br.GetInt32() - ds1.SubstitutionGroups = make([]SubstitutionGroup, numberOfSubGroups) + hasSubstitutions := ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) - for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ { - 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 { + if !hasSubstitutions { 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 { var layerStream []d2enum.LayerStreamType - if ds1.Version < 4 { //nolint:gomnd // Version number + if ds1.Version < v4 { layerStream = []d2enum.LayerStreamType{ d2enum.LayerStreamWall1, d2enum.LayerStreamFloor1, @@ -189,55 +326,100 @@ func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType { return layerStream } -func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) { - if ds1.Version >= 14 { //nolint:gomnd // Version number - numberOfNpcs := br.GetInt32() - for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ { - numPaths := br.GetInt32() - npcX := int(br.GetInt32()) - npcY := int(br.GetInt32()) - objIdx := -1 +func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { + var err error - for idx, ds1Obj := range ds1.Objects { - if ds1Obj.X == npcX && ds1Obj.Y == npcY { - objIdx = idx - break - } + if ds1.Version < v14 { + return err + } + + 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 { - ds1.loadNpcPaths(br, objIdx, int(numPaths)) + if objIdx > -1 { + 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 { - if ds1.Version >= 15 { //nolint:gomnd // Version number - br.SkipBytes(int(numPaths) * 3) //nolint:gomnd // Unknown data - } else { - br.SkipBytes(int(numPaths) * 2) //nolint:gomnd // Unknown data - } + 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 { ds1.Objects[objIdx].Paths = make([]d2path.Path, numPaths) } for pathIdx := 0; pathIdx < numPaths; pathIdx++ { newPath := d2path.Path{} - newPath.Position = d2vector.NewPosition( - float64(br.GetInt32()), - float64(br.GetInt32())) - if ds1.Version >= 15 { //nolint:gomnd // Version number - newPath.Action = int(br.GetInt32()) + px, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... + 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 } + + 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{ 0x00, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03, 0x05, 0x05, 0x06, 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 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 { 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) c := int32(dw & 0x000000FF) //nolint:gomnd // Bitmask - if ds1.Version < 7 { //nolint:gomnd // Version number + if ds1.Version < v7 { if c < int32(len(dirLookup)) { c = dirLookup[c] } @@ -294,4 +479,6 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e } } } + + return err } diff --git a/d2common/d2fileformats/d2dt1/dt1.go b/d2common/d2fileformats/d2dt1/dt1.go index ed0257b0..3868f456 100644 --- a/d2common/d2fileformats/d2dt1/dt1.go +++ b/d2common/d2fileformats/d2dt1/dt1.go @@ -22,55 +22,146 @@ const ( BlockFormatIsometric BlockDataFormat = 1 ) +const ( + numUnknownHeaderBytes = 260 + knownMajorVersion = 7 + knownMinorVersion = 6 + numUnknownTileBytes1 = 4 + numUnknownTileBytes2 = 4 + numUnknownTileBytes3 = 7 + numUnknownTileBytes4 = 12 +) + // LoadDT1 loads a DT1 record -//nolint:funlen // Can't reduce +//nolint:funlen,gocognit,gocyclo // Can't reduce func LoadDT1(fileData []byte) (*DT1, error) { result := &DT1{} br := d2datautils.CreateStreamReader(fileData) - ver1 := br.GetInt32() - ver2 := br.GetInt32() - if ver1 != 7 || ver2 != 6 { - return nil, fmt.Errorf("expected to have a version of 7.6, but got %d.%d instead", ver1, ver2) + var err error + + 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() - br.SetPosition(uint64(br.GetInt32())) + if majorVersion != knownMajorVersion || minorVersion != knownMinorVersion { + 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) for tileIdx := range result.Tiles { - newTile := Tile{} - newTile.Direction = br.GetInt32() - newTile.RoofHeight = br.GetInt16() - newTile.MaterialFlags = NewMaterialFlags(br.GetUInt16()) - newTile.Height = br.GetInt32() - newTile.Width = br.GetInt32() + tile := Tile{} - br.SkipBytes(4) //nolint:gomnd // Unknown data - - newTile.Type = br.GetInt32() - 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()) + tile.Direction, err = br.ReadInt32() + if err != nil { + return nil, err } - br.SkipBytes(7) //nolint:gomnd // Unknown data + tile.RoofHeight, err = br.ReadInt16() + if err != nil { + return nil, err + } - newTile.blockHeaderPointer = br.GetInt32() - newTile.blockHeaderSize = br.GetInt32() - newTile.Blocks = make([]Block, br.GetInt32()) + var matFlagBytes uint16 - 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 { @@ -78,14 +169,32 @@ func LoadDT1(fileData []byte) (*DT1, error) { br.SetPosition(uint64(tile.blockHeaderPointer)) for blockIdx := range tile.Blocks { - result.Tiles[tileIdx].Blocks[blockIdx].X = br.GetInt16() - result.Tiles[tileIdx].Blocks[blockIdx].Y = br.GetInt16() + result.Tiles[tileIdx].Blocks[blockIdx].X, err = br.ReadInt16() + 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 - result.Tiles[tileIdx].Blocks[blockIdx].GridX = br.GetByte() - result.Tiles[tileIdx].Blocks[blockIdx].GridY = br.GetByte() - formatValue := br.GetInt16() + result.Tiles[tileIdx].Blocks[blockIdx].GridX, err = br.ReadByte() + if err != nil { + 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 { 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].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 - 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 { 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 } } diff --git a/d2common/d2fileformats/d2mpq/mpq_stream.go b/d2common/d2fileformats/d2mpq/mpq_stream.go index dfd43802..a3327e35 100644 --- a/d2common/d2fileformats/d2mpq/mpq_stream.go +++ b/d2common/d2fileformats/d2mpq/mpq_stream.go @@ -223,7 +223,7 @@ func (v *Stream) loadBlock(blockIndex, expectedLength uint32) ([]byte, error) { 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) { compressionType := data[0] @@ -237,9 +237,9 @@ func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) { case 0x10: // BZip2 return []byte{}, errors.New("bzip2 decompression not supported") case 0x80: // IMA ADPCM Stereo - return d2compression.WavDecompress(data[1:], 2), nil + return d2compression.WavDecompress(data[1:], 2) case 0x40: // IMA ADPCM Mono - return d2compression.WavDecompress(data[1:], 1), nil + return d2compression.WavDecompress(data[1:], 1) case 0x12: return []byte{}, errors.New("lzma decompression not supported") // Combos @@ -250,8 +250,11 @@ func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) { // sparse then bzip2 return []byte{}, errors.New("sparse decompression + bzip2 decompression not supported") case 0x41: - sinput := d2compression.HuffmanDecompress(data[1:]) - sinput = d2compression.WavDecompress(sinput, 1) + sinput, err := d2compression.WavDecompress(d2compression.HuffmanDecompress(data[1:]), 1) + if err != nil { + return nil, err + } + tmp := make([]byte, len(sinput)) copy(tmp, sinput) @@ -262,8 +265,11 @@ func decompressMulti(data []byte /*expectedLength*/, _ uint32) ([]byte, error) { // return MpqWavCompression.Decompress(new MemoryStream(result), 1); return []byte{}, errors.New("pk + mpqwav decompression not supported") case 0x81: - sinput := d2compression.HuffmanDecompress(data[1:]) - sinput = d2compression.WavDecompress(sinput, 2) + sinput, err := d2compression.WavDecompress(d2compression.HuffmanDecompress(data[1:]), 2) + if err != nil { + return nil, err + } + tmp := make([]byte, len(sinput)) copy(tmp, sinput) diff --git a/d2common/d2fileformats/d2tbl/text_dictionary.go b/d2common/d2fileformats/d2tbl/text_dictionary.go index e350dc46..b744bfc9 100644 --- a/d2common/d2fileformats/d2tbl/text_dictionary.go +++ b/d2common/d2fileformats/d2tbl/text_dictionary.go @@ -10,6 +10,97 @@ import ( // TextDictionary is a string map 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 { IsActive bool Index uint16 @@ -30,71 +121,46 @@ func LoadTextDictionary(dictionaryData []byte) (TextDictionary, error) { br := d2datautils.CreateStreamReader(dictionaryData) // skip past the CRC - br.ReadBytes(crcByteCount) + _, _ = br.ReadBytes(crcByteCount) - numberOfElements := br.GetUInt16() - hashTableSize := br.GetUInt32() + var err error + + numberOfElements, err := br.ReadUInt16() + if err != nil { + return nil, err + } + + hashTableSize, err := br.ReadUInt32() + if err != nil { + return nil, err + } // Version (always 0) - if _, err := br.ReadByte(); err != nil { + if _, err = br.ReadByte(); err != nil { return nil, errors.New("error reading Version record") } - br.GetUInt32() // 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 + _, _ = br.ReadUInt32() // StringOffset + + // 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) for i := 0; i < int(numberOfElements); i++ { - elementIndex[i] = br.GetUInt16() - } - - 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(), + elementIndex[i], err = br.ReadUInt16() + if err != nil { + return nil, err } } - for idx, hashEntry := range hashEntries { - if br.EOF() { - return nil, errors.New("unexpected end of text dictionary file") - } + hashEntries := make([]*textDictionaryHashEntry, hashTableSize) - if !hashEntry.IsActive { - continue - } - - 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 - } + err = lookupTable.loadHashEntries(hashEntries, br) + if err != nil { + return nil, err } return lookupTable, nil diff --git a/d2game/d2gamescreen/blizzard_intro.go b/d2game/d2gamescreen/blizzard_intro.go index c97f8e56..2bb13f65 100644 --- a/d2game/d2gamescreen/blizzard_intro.go +++ b/d2game/d2gamescreen/blizzard_intro.go @@ -29,5 +29,8 @@ func (v *BlizzardIntro) OnLoad(loading d2screen.LoadingState) { loading.Progress(fiftyPercent) - v.videoDecoder = d2video.CreateBinkDecoder(videoBytes) + v.videoDecoder, err = d2video.CreateBinkDecoder(videoBytes) + if err != nil { + loading.Error(err) + } } diff --git a/d2game/d2gamescreen/cinematics.go b/d2game/d2gamescreen/cinematics.go index ee3171e3..de3c95e6 100644 --- a/d2game/d2gamescreen/cinematics.go +++ b/d2game/d2gamescreen/cinematics.go @@ -176,7 +176,11 @@ func (v *Cinematics) playVideo(path string) { 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