OpenDiablo2/d2common/d2fileformats/d2dt1/dt1.go

332 lines
7.0 KiB
Go

package d2dt1
import (
"fmt"
"sort"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
)
// BlockDataFormat represents the format of the block data
type BlockDataFormat int16
const (
// BlockFormatRLE specifies the block format is RLE encoded
BlockFormatRLE BlockDataFormat = 0
// BlockFormatIsometric specifies the block format isometrically encoded
BlockFormatIsometric BlockDataFormat = 1
)
const (
numUnknownHeaderBytes = 260
knownMajorVersion = 7
knownMinorVersion = 6
numUnknownTileBytes1 = 4
numUnknownTileBytes2 = 4
numUnknownTileBytes3 = 7
numUnknownTileBytes4 = 12
numUnknownBlockBytes = 2
)
// DT1 represents a DT1 file.
type DT1 struct {
majorVersion int32
minorVersion int32
numberOfTiles int32
bodyPosition int32
Tiles []Tile
}
// New creates a new DT1
func New() *DT1 {
result := &DT1{
majorVersion: knownMajorVersion,
minorVersion: knownMinorVersion,
}
return result
}
// LoadDT1 loads a DT1 record
//nolint:funlen,gocognit,gocyclo // Can't reduce
func LoadDT1(fileData []byte) (*DT1, error) {
result := &DT1{}
br := d2datautils.CreateStreamReader(fileData)
var err error
result.majorVersion, err = br.ReadInt32()
if err != nil {
return nil, err
}
result.minorVersion, err = br.ReadInt32()
if err != nil {
return nil, err
}
if result.majorVersion != knownMajorVersion || result.minorVersion != knownMinorVersion {
const fmtErr = "expected to have a version of 7.6, but got %d.%d instead"
return nil, fmt.Errorf(fmtErr, result.majorVersion, result.minorVersion)
}
br.SkipBytes(numUnknownHeaderBytes)
result.numberOfTiles, err = br.ReadInt32()
if err != nil {
return nil, err
}
result.bodyPosition, err = br.ReadInt32()
if err != nil {
return nil, err
}
br.SetPosition(uint64(result.bodyPosition))
result.Tiles = make([]Tile, result.numberOfTiles)
for tileIdx := range result.Tiles {
tile := Tile{}
tile.Direction, err = br.ReadInt32()
if err != nil {
return nil, err
}
tile.RoofHeight, err = br.ReadInt16()
if err != nil {
return nil, err
}
var matFlagBytes uint16
matFlagBytes, err = br.ReadUInt16()
if err != nil {
return nil, err
}
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
}
tile.unknown2, err = br.ReadBytes(numUnknownTileBytes2)
if err != nil {
return nil, err
}
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)
if err != nil {
return nil, err
}
result.Tiles[tileIdx] = tile
}
for tileIdx := range result.Tiles {
tile := &result.Tiles[tileIdx]
br.SetPosition(uint64(tile.blockHeaderPointer))
for blockIdx := range tile.Blocks {
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(numUnknownBlockBytes)
if err != nil {
return nil, err
}
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
}
result.Tiles[tileIdx].Blocks[blockIdx].format, err = br.ReadInt16()
if err != nil {
return nil, err
}
result.Tiles[tileIdx].Blocks[blockIdx].Length, err = br.ReadInt32()
if err != nil {
return nil, err
}
br.SkipBytes(numUnknownBlockBytes)
if err != nil {
return nil, err
}
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, err := br.ReadBytes(int(block.Length))
if err != nil {
return nil, err
}
tile.Blocks[blockIndex].EncodedData = encodedData
}
}
return result, nil
}
// Marshal encodes dt1 data back to byte slice
func (d *DT1) Marshal() []byte {
sw := d2datautils.CreateStreamWriter()
// header
sw.PushInt32(d.majorVersion)
sw.PushInt32(d.minorVersion)
sw.PushBytes(d.unknownHeaderBytes()...)
sw.PushInt32(d.numberOfTiles)
sw.PushInt32(d.bodyPosition)
// Step 1 - encoding tiles headers
for i := 0; i < len(d.Tiles); i++ {
sw.PushInt32(d.Tiles[i].Direction)
sw.PushInt16(d.Tiles[i].RoofHeight)
sw.PushUint16(d.Tiles[i].MaterialFlags.Encode())
sw.PushInt32(d.Tiles[i].Height)
sw.PushInt32(d.Tiles[i].Width)
sw.PushBytes(d.Tiles[i].unknown1()...)
sw.PushInt32(d.Tiles[i].Type)
sw.PushInt32(d.Tiles[i].Style)
sw.PushInt32(d.Tiles[i].Sequence)
sw.PushInt32(d.Tiles[i].RarityFrameIndex)
sw.PushBytes(d.Tiles[i].unknown2...)
for _, j := range d.Tiles[i].SubTileFlags {
sw.PushBytes(j.Encode())
}
sw.PushBytes(d.Tiles[i].unknown3()...)
sw.PushInt32(d.Tiles[i].blockHeaderPointer)
sw.PushInt32(d.Tiles[i].blockHeaderSize)
sw.PushInt32(int32(len(d.Tiles[i].Blocks)))
sw.PushBytes(d.Tiles[i].unknown4()...)
}
// we must sort blocks first
blocks := make(map[int][]Block)
for i := range d.Tiles {
blocks[int(d.Tiles[i].blockHeaderPointer)] = d.Tiles[i].Blocks
}
keys := make([]int, 0, len(blocks))
for i := range blocks {
keys = append(keys, i)
}
sort.Ints(keys)
// Step 2 - encoding blocks
for i := 0; i < len(keys); i++ {
// Step 2.1 - encoding blocks' header
for j := range blocks[keys[i]] {
sw.PushInt16(blocks[keys[i]][j].X)
sw.PushInt16(blocks[keys[i]][j].Y)
sw.PushBytes(blocks[keys[i]][j].unknown1()...)
sw.PushBytes(blocks[keys[i]][j].GridX)
sw.PushBytes(blocks[keys[i]][j].GridY)
sw.PushInt16(blocks[keys[i]][j].format)
sw.PushInt32(blocks[keys[i]][j].Length)
sw.PushBytes(blocks[keys[i]][j].unknown2()...)
sw.PushInt32(blocks[keys[i]][j].FileOffset)
}
// Step 2.2 - encoding blocks' data
for j := range blocks[keys[i]] {
sw.PushBytes(blocks[keys[i]][j].EncodedData...)
}
}
return sw.GetBytes()
}
func (d *DT1) unknownHeaderBytes() []byte {
result := make([]byte, numUnknownHeaderBytes)
return result
}