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)
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))

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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++ {

View File

@ -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]

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}
}

View File

@ -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