From 9f565740669560e9b2d9d4290550dd329b107bff Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Fri, 5 Feb 2021 12:52:51 +0100 Subject: [PATCH 01/17] data encoder: ds1 --- d2common/d2fileformats/d2ds1/ds1.go | 171 +++++++++++++++++- .../d2ds1/floor_shadow_record.go | 7 +- d2common/d2fileformats/d2ds1/wall_record.go | 7 +- d2core/d2map/d2maprenderer/renderer.go | 8 +- d2core/d2map/d2maprenderer/tile_cache.go | 6 +- 5 files changed, 180 insertions(+), 19 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index a27b8e7e..50c9a5bd 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -43,6 +43,10 @@ type DS1 struct { NumberOfShadowLayers int32 // ShadowNum number of shadow layer used NumberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used SubstitutionGroupsNum int32 // SubstitutionGroupsNum number of substitution groups, datas between objects & NPC paths + unknown1 []byte + layerStreamTypes []d2enum.LayerStreamType + unknown2 uint32 + npcIndexes []int } // LoadDS1 loads the specified DS1 file @@ -127,7 +131,10 @@ func LoadDS1(fileData []byte) (*DS1, error) { 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 + ds1.unknown1, err = br.ReadBytes(8) //nolint:gomnd // We don't know what's here + if err != nil { + return nil, err + } } if ds1.Version >= v4 { @@ -146,7 +153,7 @@ func LoadDS1(fileData []byte) (*DS1, error) { } } - layerStream := ds1.setupStreamLayerTypes() + ds1.layerStreamTypes = ds1.setupStreamLayerTypes() ds1.Tiles = make([][]TileRecord, ds1.Height) @@ -160,7 +167,7 @@ func LoadDS1(fileData []byte) (*DS1, error) { } } - err = ds1.loadLayerStreams(br, layerStream) + err = ds1.loadLayerStreams(br) if err != nil { return nil, err } @@ -245,7 +252,10 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error { } if ds1.Version >= v18 { - _, _ = br.ReadUInt32() + ds1.unknown2, err = br.ReadUInt32() + if err != nil { + return err + } } numberOfSubGroups, err := br.ReadInt32() @@ -360,6 +370,8 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { for idx, ds1Obj := range ds1.Objects { if ds1Obj.X == int(npcX) && ds1Obj.Y == int(npcY) { objIdx = idx + ds1.npcIndexes = append(ds1.npcIndexes, idx) + break } } @@ -418,7 +430,7 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) return err } -func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2enum.LayerStreamType) error { +func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { var err error var dirLookup = []int32{ @@ -427,8 +439,8 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e 0x0F, 0x10, 0x11, 0x12, 0x14, } - for lIdx := range layerStream { - layerStreamType := layerStream[lIdx] + for lIdx := range ds1.layerStreamTypes { + layerStreamType := ds1.layerStreamTypes[lIdx] for y := 0; y < int(ds1.Height); y++ { for x := 0; x < int(ds1.Width); x++ { @@ -445,7 +457,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e ds1.Tiles[y][x].Walls[wallIndex].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Walls[wallIndex].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Walls[wallIndex].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Walls[wallIndex].Hidden = byte((dw&0x80000000)>>31) > 0 //nolint:gomnd // Bitmask + ds1.Tiles[y][x].Walls[wallIndex].hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) @@ -466,14 +478,14 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e ds1.Tiles[y][x].Floors[floorIndex].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Floors[floorIndex].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Floors[floorIndex].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Floors[floorIndex].Hidden = byte((dw&0x80000000)>>31) > 0 //nolint:gomnd // Bitmask + ds1.Tiles[y][x].Floors[floorIndex].hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask case d2enum.LayerStreamShadow: ds1.Tiles[y][x].Shadows[0].Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Shadows[0].Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Shadows[0].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Shadows[0].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Shadows[0].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Shadows[0].Hidden = byte((dw&0x80000000)>>31) > 0 //nolint:gomnd // Bitmask + ds1.Tiles[y][x].Shadows[0].hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask case d2enum.LayerStreamSubstitute: ds1.Tiles[y][x].Substitutions[0].Unknown = dw } @@ -483,3 +495,142 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e return err } + +// Marshal encodes ds1 back to byte slice +// nolint:funlen,gocognit // no need to change +func (ds1 *DS1) Marshal() []byte { + // create stream writer + sw := d2datautils.CreateStreamWriter() + + // Step 1 - encode header + sw.PushInt32(ds1.Version) + sw.PushInt32(ds1.Width - 1) + sw.PushInt32(ds1.Height - 1) + + if ds1.Version >= v8 { + sw.PushInt32(ds1.Act - 1) + } + + if ds1.Version >= v10 { + sw.PushInt32(ds1.SubstitutionType) + } + + if ds1.Version >= v3 { + sw.PushInt32(int32(len(ds1.Files))) + + for _, i := range ds1.Files { + sw.PushBytes([]byte(i)...) + + // separator + sw.PushBytes(0) + } + } + + if ds1.Version >= v9 && ds1.Version <= v13 { + sw.PushBytes(ds1.unknown1...) + } + + if ds1.Version >= v4 { + sw.PushInt32(ds1.NumberOfWalls) + + if ds1.Version >= v16 { + sw.PushInt32(ds1.NumberOfFloors) + } + } + + // Step 2 - encode layers + for lIdx := range ds1.layerStreamTypes { + layerStreamType := ds1.layerStreamTypes[lIdx] + + for y := 0; y < int(ds1.Height); y++ { + for x := 0; x < int(ds1.Width); x++ { + dw := uint32(0) + + switch layerStreamType { + case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: + wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) + dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Prop1) & 0xFF //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Style) & 0x3F) << 20 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].hidden) & 0x01) << 31 //nolint:gomnd // Bitmask + case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, + d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: + wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) + dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Type) + dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Zero) & 0xFFFFFF00) << 8 //nolint:gomnd // Bitmask + + case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: + floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) + dw |= uint32(ds1.Tiles[y][x].Floors[floorIndex].Prop1) & 0xFF //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].Style) & 0x3F) << 20 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].hidden) & 0x01) << 31 //nolint:gomnd // Bitmask + case d2enum.LayerStreamShadow: + dw |= uint32(ds1.Tiles[y][x].Shadows[0].Prop1) & 0xFF //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Shadows[0].Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Shadows[0].Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Shadows[0].Style) & 0x3F) << 20 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Shadows[0].Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Shadows[0].hidden) & 0x01) << 31 //nolint:gomnd // Bitmask + case d2enum.LayerStreamSubstitute: + dw = ds1.Tiles[y][x].Substitutions[0].Unknown + } + + sw.PushUint32(dw) + } + } + } + + // Step 3 - encode objects + if !(ds1.Version < v2) { + sw.PushInt32(int32(len(ds1.Objects))) + + for _, i := range ds1.Objects { + sw.PushUint32(uint32(i.Type)) + sw.PushUint32(uint32(i.ID)) + sw.PushUint32(uint32(i.X)) + sw.PushUint32(uint32(i.Y)) + sw.PushUint32(uint32(i.Flags)) + } + } + + // Step 4 - encode substitutions + if ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) { + sw.PushUint32(ds1.unknown2) + + sw.PushUint32(uint32(len(ds1.SubstitutionGroups))) + + for _, i := range ds1.SubstitutionGroups { + sw.PushInt32(i.TileX) + sw.PushInt32(i.TileY) + sw.PushInt32(i.WidthInTiles) + sw.PushInt32(i.HeightInTiles) + sw.PushInt32(i.Unknown) + } + } + + // Step 5.1 - encode npc's + sw.PushUint32(uint32(len(ds1.npcIndexes))) + + // Step 5.2 - enoce npcs' paths + for _, i := range ds1.npcIndexes { + sw.PushUint32(uint32(len(ds1.Objects[i].Paths))) + sw.PushUint32(uint32(ds1.Objects[i].X)) + sw.PushUint32(uint32(ds1.Objects[i].Y)) + + for _, j := range ds1.Objects[i].Paths { + sw.PushUint32(uint32(j.Position.X())) + sw.PushUint32(uint32(j.Position.Y())) + + if ds1.Version >= v15 { + sw.PushUint32(uint32(j.Action)) + } + } + } + + return sw.GetBytes() +} diff --git a/d2common/d2fileformats/d2ds1/floor_shadow_record.go b/d2common/d2fileformats/d2ds1/floor_shadow_record.go index 0b569457..ee2808ad 100644 --- a/d2common/d2fileformats/d2ds1/floor_shadow_record.go +++ b/d2common/d2fileformats/d2ds1/floor_shadow_record.go @@ -7,8 +7,13 @@ type FloorShadowRecord struct { Unknown1 byte Style byte Unknown2 byte - Hidden bool + hidden byte RandomIndex byte Animated bool YAdjust int } + +// Hidden returns if floor/shadow is hidden +func (f *FloorShadowRecord) Hidden() bool { + return f.hidden > 0 +} diff --git a/d2common/d2fileformats/d2ds1/wall_record.go b/d2common/d2fileformats/d2ds1/wall_record.go index 5a6571fa..4c879eef 100644 --- a/d2common/d2fileformats/d2ds1/wall_record.go +++ b/d2common/d2fileformats/d2ds1/wall_record.go @@ -11,7 +11,12 @@ type WallRecord struct { Unknown1 byte Style byte Unknown2 byte - Hidden bool + hidden byte RandomIndex byte YAdjust int } + +// Hidden returns if wall is hidden +func (w *WallRecord) Hidden() bool { + return w.hidden > 0 +} diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index 0691d582..e7fb8b67 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -359,19 +359,19 @@ func (mr *MapRenderer) renderPass4(target d2interface.Surface, startX, startY, e func (mr *MapRenderer) renderTilePass1(tile *d2mapengine.MapTile, target d2interface.Surface) { for _, wall := range tile.Components.Walls { - if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() { + if !wall.Hidden() && wall.Prop1 != 0 && wall.Type.LowerWall() { mr.renderWall(wall, mr.viewport, target) } } for _, floor := range tile.Components.Floors { - if !floor.Hidden && floor.Prop1 != 0 { + if !floor.Hidden() && floor.Prop1 != 0 { mr.renderFloor(floor, target) } } for _, shadow := range tile.Components.Shadows { - if !shadow.Hidden && shadow.Prop1 != 0 { + if !shadow.Hidden() && shadow.Prop1 != 0 { mr.renderShadow(shadow, target) } } @@ -379,7 +379,7 @@ func (mr *MapRenderer) renderTilePass1(tile *d2mapengine.MapTile, target d2inter func (mr *MapRenderer) renderTilePass2(tile *d2mapengine.MapTile, target d2interface.Surface) { for _, wall := range tile.Components.Walls { - if !wall.Hidden && wall.Type.UpperWall() { + if !wall.Hidden() && wall.Type.UpperWall() { mr.renderWall(wall, mr.viewport, target) } } diff --git a/d2core/d2map/d2maprenderer/tile_cache.go b/d2core/d2map/d2maprenderer/tile_cache.go index 56991a59..0503f732 100644 --- a/d2core/d2map/d2maprenderer/tile_cache.go +++ b/d2core/d2map/d2maprenderer/tile_cache.go @@ -34,19 +34,19 @@ func (mr *MapRenderer) generateTileCache() { tile := &tiles[idx] for i := range tile.Components.Floors { - if !tile.Components.Floors[i].Hidden && tile.Components.Floors[i].Prop1 != 0 { + if !tile.Components.Floors[i].Hidden() && tile.Components.Floors[i].Prop1 != 0 { mr.generateFloorCache(&tile.Components.Floors[i]) } } for i := range tile.Components.Shadows { - if !tile.Components.Shadows[i].Hidden && tile.Components.Shadows[i].Prop1 != 0 { + if !tile.Components.Shadows[i].Hidden() && tile.Components.Shadows[i].Prop1 != 0 { mr.generateShadowCache(&tile.Components.Shadows[i]) } } for i := range tile.Components.Walls { - if !tile.Components.Walls[i].Hidden && tile.Components.Walls[i].Prop1 != 0 { + if !tile.Components.Walls[i].Hidden() && tile.Components.Walls[i].Prop1 != 0 { mr.generateWallCache(&tile.Components.Walls[i]) } } From 3dafb3ebcdce17cb425fc6f45d52e60bc389566d Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Fri, 5 Feb 2021 14:07:51 +0100 Subject: [PATCH 02/17] dt1 encoder: moved record encoders and decoders to appropriate files --- d2common/d2fileformats/d2ds1/ds1.go | 44 +++---------------- .../d2ds1/floor_shadow_record.go | 22 ++++++++++ d2common/d2fileformats/d2ds1/wall_record.go | 22 ++++++++++ 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 50c9a5bd..6717f3e2 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -452,12 +452,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { switch layerStreamType { case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) - ds1.Tiles[y][x].Walls[wallIndex].Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Walls[wallIndex].Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Walls[wallIndex].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Walls[wallIndex].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Walls[wallIndex].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Walls[wallIndex].hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask + ds1.Tiles[y][x].Walls[wallIndex].Decode(dw) //nolint:gomnd // Bitmask case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) @@ -473,19 +468,9 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & 0xFFFFFF00) >> 8) //nolint:gomnd // Bitmask case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) - ds1.Tiles[y][x].Floors[floorIndex].Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Floors[floorIndex].Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Floors[floorIndex].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Floors[floorIndex].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Floors[floorIndex].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Floors[floorIndex].hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask + ds1.Tiles[y][x].Floors[floorIndex].Decode(dw) case d2enum.LayerStreamShadow: - ds1.Tiles[y][x].Shadows[0].Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Shadows[0].Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Shadows[0].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Shadows[0].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Shadows[0].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask - ds1.Tiles[y][x].Shadows[0].hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask + ds1.Tiles[y][x].Shadows[0].Decode(dw) case d2enum.LayerStreamSubstitute: ds1.Tiles[y][x].Substitutions[0].Unknown = dw } @@ -497,7 +482,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { } // Marshal encodes ds1 back to byte slice -// nolint:funlen,gocognit // no need to change +// nolint:funlen,gocognit,gocyclo // no need to change func (ds1 *DS1) Marshal() []byte { // create stream writer sw := d2datautils.CreateStreamWriter() @@ -549,12 +534,7 @@ func (ds1 *DS1) Marshal() []byte { switch layerStreamType { case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) - dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Prop1) & 0xFF //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Style) & 0x3F) << 20 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].hidden) & 0x01) << 31 //nolint:gomnd // Bitmask + dw = ds1.Tiles[y][x].Walls[wallIndex].Encode() case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) @@ -563,19 +543,9 @@ func (ds1 *DS1) Marshal() []byte { case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) - dw |= uint32(ds1.Tiles[y][x].Floors[floorIndex].Prop1) & 0xFF //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].Style) & 0x3F) << 20 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Floors[floorIndex].hidden) & 0x01) << 31 //nolint:gomnd // Bitmask + dw = ds1.Tiles[y][x].Floors[floorIndex].Encode() case d2enum.LayerStreamShadow: - dw |= uint32(ds1.Tiles[y][x].Shadows[0].Prop1) & 0xFF //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Shadows[0].Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Shadows[0].Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Shadows[0].Style) & 0x3F) << 20 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Shadows[0].Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask - dw |= (uint32(ds1.Tiles[y][x].Shadows[0].hidden) & 0x01) << 31 //nolint:gomnd // Bitmask + dw = ds1.Tiles[y][x].Shadows[0].Encode() case d2enum.LayerStreamSubstitute: dw = ds1.Tiles[y][x].Substitutions[0].Unknown } diff --git a/d2common/d2fileformats/d2ds1/floor_shadow_record.go b/d2common/d2fileformats/d2ds1/floor_shadow_record.go index ee2808ad..61da47d1 100644 --- a/d2common/d2fileformats/d2ds1/floor_shadow_record.go +++ b/d2common/d2fileformats/d2ds1/floor_shadow_record.go @@ -17,3 +17,25 @@ type FloorShadowRecord struct { func (f *FloorShadowRecord) Hidden() bool { return f.hidden > 0 } + +// Decode decodes floor-shadow record +func (f *FloorShadowRecord) Decode(dw uint32) { + f.Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask + f.Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask + f.Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask + f.Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask + f.Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask + f.hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask +} + +// Encode encodes floor-shadow record +func (f *FloorShadowRecord) Encode() (dw uint32) { + dw |= uint32(f.Prop1) & 0xFF //nolint:gomnd // Bitmask + dw |= (uint32(f.Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask + dw |= (uint32(f.Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask + dw |= (uint32(f.Style) & 0x3F) << 20 //nolint:gomnd // Bitmask + dw |= (uint32(f.Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask + dw |= (uint32(f.hidden) & 0x01) << 31 //nolint:gomnd // Bitmask + + return dw +} diff --git a/d2common/d2fileformats/d2ds1/wall_record.go b/d2common/d2fileformats/d2ds1/wall_record.go index 4c879eef..4124a74a 100644 --- a/d2common/d2fileformats/d2ds1/wall_record.go +++ b/d2common/d2fileformats/d2ds1/wall_record.go @@ -20,3 +20,25 @@ type WallRecord struct { func (w *WallRecord) Hidden() bool { return w.hidden > 0 } + +// Encode encodes wall record +func (w *WallRecord) Encode() (dw uint32) { + dw |= uint32(w.Prop1) & 0xFF //nolint:gomnd // Bitmask + dw |= (uint32(w.Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask + dw |= (uint32(w.Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask + dw |= (uint32(w.Style) & 0x3F) << 20 //nolint:gomnd // Bitmask + dw |= (uint32(w.Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask + dw |= (uint32(w.hidden) & 0x01) << 31 //nolint:gomnd // Bitmask + + return dw +} + +// Decode decodes wall record +func (w *WallRecord) Decode(dw uint32) { + w.Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask + w.Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask + w.Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask + w.Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask + w.Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask + w.hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask +} From 5702d96cac7f188c80bba06adb211f9759062329 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Fri, 5 Feb 2021 14:45:22 +0100 Subject: [PATCH 03/17] ds1 encoder: fixed bug, when decoded and encoded back data wasn't the same = records' encoding methods was rewritten to use streamWriter.Pushbit --- d2common/d2datautils/stream_writer.go | 62 ++++++++++++++++++- d2common/d2fileformats/d2ds1/ds1.go | 10 +-- .../d2ds1/floor_shadow_record.go | 22 ++++--- d2common/d2fileformats/d2ds1/wall_record.go | 23 +++---- 4 files changed, 90 insertions(+), 27 deletions(-) diff --git a/d2common/d2datautils/stream_writer.go b/d2common/d2datautils/stream_writer.go index 432fcdbb..0656a658 100644 --- a/d2common/d2datautils/stream_writer.go +++ b/d2common/d2datautils/stream_writer.go @@ -4,7 +4,9 @@ import "bytes" // StreamWriter allows you to create a byte array by streaming in writes of various sizes type StreamWriter struct { - data *bytes.Buffer + data *bytes.Buffer + bitOffset int + bitCache byte } // CreateStreamWriter creates a new StreamWriter instance @@ -28,6 +30,64 @@ func (v *StreamWriter) PushBytes(b ...byte) { } } +func (v *StreamWriter) PushBit(b bool) { + if b { + v.bitCache |= (1 << v.bitOffset) + } + v.bitOffset++ + + if v.bitOffset != bitsPerByte { + return + } + + v.PushBytes(v.bitCache) + v.bitCache = 0 + v.bitOffset = 0 +} + +func (v *StreamWriter) PushBits(b byte, bits int) { + val := b + for i := 0; i < bits; i++ { + if val&1 == 1 { + v.PushBit(true) + } else { + v.PushBit(false) + } + + val >>= 1 + } +} + +func (v *StreamWriter) PushBits16(b uint16, bits int) { + val := b + for i := 0; i < bits; i++ { + if val&1 == 1 { + v.PushBit(true) + } else { + v.PushBit(false) + } + val >>= 1 + } +} + +func (v *StreamWriter) PushBits32(b uint32, bits int) { + val := b + for i := 0; i < bits; i++ { + if val&1 == 1 { + v.PushBit(true) + } else { + v.PushBit(false) + } + val >>= 1 + } +} + +func (v *StreamWriter) ForcePushBits() { + for i := 0; i < bitsPerByte-v.bitOffset; i++ { + v.PushBit(0 != 0) + } +} + // PushInt16 writes a int16 word to the stream func (v *StreamWriter) PushInt16(val int16) { v.PushUint16(uint16(val)) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 6717f3e2..f469931e 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -534,23 +534,23 @@ func (ds1 *DS1) Marshal() []byte { switch layerStreamType { case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) - dw = ds1.Tiles[y][x].Walls[wallIndex].Encode() + ds1.Tiles[y][x].Walls[wallIndex].Encode(sw) case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Type) dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Zero) & 0xFFFFFF00) << 8 //nolint:gomnd // Bitmask + sw.PushUint32(dw) case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) - dw = ds1.Tiles[y][x].Floors[floorIndex].Encode() + ds1.Tiles[y][x].Floors[floorIndex].Encode(sw) case d2enum.LayerStreamShadow: - dw = ds1.Tiles[y][x].Shadows[0].Encode() + ds1.Tiles[y][x].Shadows[0].Encode(sw) case d2enum.LayerStreamSubstitute: - dw = ds1.Tiles[y][x].Substitutions[0].Unknown + sw.PushUint32(ds1.Tiles[y][x].Substitutions[0].Unknown) } - sw.PushUint32(dw) } } } diff --git a/d2common/d2fileformats/d2ds1/floor_shadow_record.go b/d2common/d2fileformats/d2ds1/floor_shadow_record.go index 61da47d1..7713cc62 100644 --- a/d2common/d2fileformats/d2ds1/floor_shadow_record.go +++ b/d2common/d2fileformats/d2ds1/floor_shadow_record.go @@ -1,5 +1,9 @@ package d2ds1 +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" +) + // FloorShadowRecord represents a floor or shadow record in a DS1 file. type FloorShadowRecord struct { Prop1 byte @@ -28,14 +32,12 @@ func (f *FloorShadowRecord) Decode(dw uint32) { f.hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask } -// Encode encodes floor-shadow record -func (f *FloorShadowRecord) Encode() (dw uint32) { - dw |= uint32(f.Prop1) & 0xFF //nolint:gomnd // Bitmask - dw |= (uint32(f.Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask - dw |= (uint32(f.Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask - dw |= (uint32(f.Style) & 0x3F) << 20 //nolint:gomnd // Bitmask - dw |= (uint32(f.Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask - dw |= (uint32(f.hidden) & 0x01) << 31 //nolint:gomnd // Bitmask - - return dw +// Encode adds Floor's bits to stream writter given +func (f *FloorShadowRecord) Encode(sw *d2datautils.StreamWriter) { + sw.PushBits32(uint32(f.Prop1), 8) + sw.PushBits32(uint32(f.Sequence), 6) + sw.PushBits32(uint32(f.Unknown1), 6) + sw.PushBits32(uint32(f.Style), 6) + sw.PushBits32(uint32(f.Unknown2), 5) + sw.PushBits32(uint32(f.hidden), 1) } diff --git a/d2common/d2fileformats/d2ds1/wall_record.go b/d2common/d2fileformats/d2ds1/wall_record.go index 4124a74a..855594ee 100644 --- a/d2common/d2fileformats/d2ds1/wall_record.go +++ b/d2common/d2fileformats/d2ds1/wall_record.go @@ -1,6 +1,9 @@ package d2ds1 -import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) // WallRecord represents a wall record. type WallRecord struct { @@ -21,16 +24,14 @@ func (w *WallRecord) Hidden() bool { return w.hidden > 0 } -// Encode encodes wall record -func (w *WallRecord) Encode() (dw uint32) { - dw |= uint32(w.Prop1) & 0xFF //nolint:gomnd // Bitmask - dw |= (uint32(w.Sequence) & 0x3F) << 8 //nolint:gomnd // Bitmask - dw |= (uint32(w.Unknown1) & 0xFC) << 14 //nolint:gomnd // Bitmask - dw |= (uint32(w.Style) & 0x3F) << 20 //nolint:gomnd // Bitmask - dw |= (uint32(w.Unknown2) & 0x7C) << 26 //nolint:gomnd // Bitmask - dw |= (uint32(w.hidden) & 0x01) << 31 //nolint:gomnd // Bitmask - - return dw +// Encode adds wall's record's bytes into stream writer given +func (w *WallRecord) Encode(sw *d2datautils.StreamWriter) { + sw.PushBits32(uint32(w.Prop1), 8) + sw.PushBits32(uint32(w.Sequence), 6) + sw.PushBits32(uint32(w.Unknown1), 6) + sw.PushBits32(uint32(w.Style), 6) + sw.PushBits32(uint32(w.Unknown2), 5) + sw.PushBits32(uint32(w.hidden), 1) } // Decode decodes wall record From 9227de34188380b2c3edf7f049f5e79d1993a9b2 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Fri, 5 Feb 2021 14:54:35 +0100 Subject: [PATCH 04/17] d2ds1 encoder: fixed lint errors --- d2common/d2datautils/stream_writer.go | 14 ++++++++------ d2common/d2fileformats/d2ds1/ds1.go | 1 - 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/d2common/d2datautils/stream_writer.go b/d2common/d2datautils/stream_writer.go index 0656a658..903314e5 100644 --- a/d2common/d2datautils/stream_writer.go +++ b/d2common/d2datautils/stream_writer.go @@ -30,6 +30,9 @@ func (v *StreamWriter) PushBytes(b ...byte) { } } +// PushBit pushes single bit into stream +// WARNING: if you'll use PushBit, offset'll be less then 7, and if you'll +// use another Push... method, bits'll not be pushed func (v *StreamWriter) PushBit(b bool) { if b { v.bitCache |= (1 << v.bitOffset) @@ -45,6 +48,7 @@ func (v *StreamWriter) PushBit(b bool) { v.bitOffset = 0 } +// PushBits pushes bits (with max range 8) func (v *StreamWriter) PushBits(b byte, bits int) { val := b for i := 0; i < bits; i++ { @@ -58,6 +62,7 @@ func (v *StreamWriter) PushBits(b byte, bits int) { } } +// PushBits16 pushes bits (with max range 16) func (v *StreamWriter) PushBits16(b uint16, bits int) { val := b for i := 0; i < bits; i++ { @@ -66,10 +71,12 @@ func (v *StreamWriter) PushBits16(b uint16, bits int) { } else { v.PushBit(false) } + val >>= 1 } } +// PushBits32 pushes bits (with max range 32) func (v *StreamWriter) PushBits32(b uint32, bits int) { val := b for i := 0; i < bits; i++ { @@ -78,13 +85,8 @@ func (v *StreamWriter) PushBits32(b uint32, bits int) { } else { v.PushBit(false) } - val >>= 1 - } -} -func (v *StreamWriter) ForcePushBits() { - for i := 0; i < bitsPerByte-v.bitOffset; i++ { - v.PushBit(0 != 0) + val >>= 1 } } diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index f469931e..ed4ce1f3 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -550,7 +550,6 @@ func (ds1 *DS1) Marshal() []byte { case d2enum.LayerStreamSubstitute: sw.PushUint32(ds1.Tiles[y][x].Substitutions[0].Unknown) } - } } } From b3a754a4a634596d937e9b086a7fe282c9b11bef Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Fri, 5 Feb 2021 15:05:11 +0100 Subject: [PATCH 05/17] ds1 encoder: Marshal method was splited to avoid nolint directive --- d2common/d2fileformats/d2ds1/ds1.go | 72 ++++++++++++++++------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index ed4ce1f3..87cf1a8f 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -482,7 +482,6 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { } // Marshal encodes ds1 back to byte slice -// nolint:funlen,gocognit,gocyclo // no need to change func (ds1 *DS1) Marshal() []byte { // create stream writer sw := d2datautils.CreateStreamWriter() @@ -524,35 +523,7 @@ func (ds1 *DS1) Marshal() []byte { } // Step 2 - encode layers - for lIdx := range ds1.layerStreamTypes { - layerStreamType := ds1.layerStreamTypes[lIdx] - - for y := 0; y < int(ds1.Height); y++ { - for x := 0; x < int(ds1.Width); x++ { - dw := uint32(0) - - switch layerStreamType { - case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: - wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) - ds1.Tiles[y][x].Walls[wallIndex].Encode(sw) - case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, - d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: - wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) - dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Type) - dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Zero) & 0xFFFFFF00) << 8 //nolint:gomnd // Bitmask - - sw.PushUint32(dw) - case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: - floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) - ds1.Tiles[y][x].Floors[floorIndex].Encode(sw) - case d2enum.LayerStreamShadow: - ds1.Tiles[y][x].Shadows[0].Encode(sw) - case d2enum.LayerStreamSubstitute: - sw.PushUint32(ds1.Tiles[y][x].Substitutions[0].Unknown) - } - } - } - } + ds1.encodeLayers(sw) // Step 3 - encode objects if !(ds1.Version < v2) { @@ -582,6 +553,45 @@ func (ds1 *DS1) Marshal() []byte { } } + // Step 5 - encode NPC's and its paths + ds1.encodeNPCs(sw) + + return sw.GetBytes() +} + +func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { + for lIdx := range ds1.layerStreamTypes { + layerStreamType := ds1.layerStreamTypes[lIdx] + + for y := 0; y < int(ds1.Height); y++ { + for x := 0; x < int(ds1.Width); x++ { + dw := uint32(0) + + switch layerStreamType { + case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: + wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) + ds1.Tiles[y][x].Walls[wallIndex].Encode(sw) + case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, + d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: + wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) + dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Type) + dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Zero) & 0xFFFFFF00) << 8 //nolint:gomnd // Bitmask + + sw.PushUint32(dw) + case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: + floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) + ds1.Tiles[y][x].Floors[floorIndex].Encode(sw) + case d2enum.LayerStreamShadow: + ds1.Tiles[y][x].Shadows[0].Encode(sw) + case d2enum.LayerStreamSubstitute: + sw.PushUint32(ds1.Tiles[y][x].Substitutions[0].Unknown) + } + } + } + } +} + +func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { // Step 5.1 - encode npc's sw.PushUint32(uint32(len(ds1.npcIndexes))) @@ -600,6 +610,4 @@ func (ds1 *DS1) Marshal() []byte { } } } - - return sw.GetBytes() } From aadfbbc8a60bdac6fa55bdefe264fdc4a4a86600 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Sat, 6 Feb 2021 17:23:11 +0100 Subject: [PATCH 06/17] stream writer: added warnings when bits count is greater then possible input size (in PUshBits... methods) --- d2common/d2datautils/stream_writer.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/d2common/d2datautils/stream_writer.go b/d2common/d2datautils/stream_writer.go index 903314e5..f2c384e8 100644 --- a/d2common/d2datautils/stream_writer.go +++ b/d2common/d2datautils/stream_writer.go @@ -1,6 +1,9 @@ package d2datautils -import "bytes" +import ( + "bytes" + "log" +) // StreamWriter allows you to create a byte array by streaming in writes of various sizes type StreamWriter struct { @@ -49,7 +52,12 @@ func (v *StreamWriter) PushBit(b bool) { } // PushBits pushes bits (with max range 8) +//func (v *StreamWriter) PushBits(b byte, bits int) { func (v *StreamWriter) PushBits(b byte, bits int) { + if bits > bitsPerByte { + log.Print("input bits number must be less (or equal) then 8") + } + val := b for i := 0; i < bits; i++ { if val&1 == 1 { @@ -64,6 +72,10 @@ func (v *StreamWriter) PushBits(b byte, bits int) { // PushBits16 pushes bits (with max range 16) func (v *StreamWriter) PushBits16(b uint16, bits int) { + if bits > bitsPerByte*bytesPerint16 { + log.Print("input bits number must be less (or equal) then 16") + } + val := b for i := 0; i < bits; i++ { if val&1 == 1 { @@ -78,6 +90,10 @@ func (v *StreamWriter) PushBits16(b uint16, bits int) { // PushBits32 pushes bits (with max range 32) func (v *StreamWriter) PushBits32(b uint32, bits int) { + if bits > bitsPerByte*bytesPerint32 { + log.Print("input bits number must be less (or equal) then 32") + } + val := b for i := 0; i < bits; i++ { if val&1 == 1 { From 32570d6ae5d8f3701a6b865637038047ed2f5761 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Sat, 6 Feb 2021 18:28:01 +0100 Subject: [PATCH 07/17] replaced nolint:gomnd directives with "magic numbers'" names --- d2common/d2fileformats/d2ds1/ds1.go | 15 ++++-- .../d2ds1/floor_shadow_record.go | 50 ++++++++++++++----- d2common/d2fileformats/d2ds1/wall_record.go | 32 ++++++------ 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 87cf1a8f..ed51f997 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -27,6 +27,13 @@ const ( v18 = 18 ) +const ( + wallZeroBitmask = 0xFFFFFF00 + wallZeroOffset = 8 + + wallTypeBitmask = 0x000000FF +) + // DS1 represents the "stamp" data that is used to build up maps. type DS1 struct { Files []string // FilePtr table of file string pointers @@ -452,11 +459,11 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { switch layerStreamType { case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) - ds1.Tiles[y][x].Walls[wallIndex].Decode(dw) //nolint:gomnd // Bitmask + ds1.Tiles[y][x].Walls[wallIndex].Decode(dw) case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) - c := int32(dw & 0x000000FF) //nolint:gomnd // Bitmask + c := int32(dw & wallTypeBitmask) if ds1.Version < v7 { if c < int32(len(dirLookup)) { @@ -465,7 +472,7 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { } ds1.Tiles[y][x].Walls[wallIndex].Type = d2enum.TileType(c) - ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & 0xFFFFFF00) >> 8) //nolint:gomnd // Bitmask + ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & wallZeroBitmask) >> wallZeroOffset) case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) ds1.Tiles[y][x].Floors[floorIndex].Decode(dw) @@ -575,7 +582,7 @@ func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) { d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Type) - dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Zero) & 0xFFFFFF00) << 8 //nolint:gomnd // Bitmask + dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Zero) & wallZeroBitmask) << wallZeroOffset sw.PushUint32(dw) case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2: diff --git a/d2common/d2fileformats/d2ds1/floor_shadow_record.go b/d2common/d2fileformats/d2ds1/floor_shadow_record.go index 7713cc62..dccf7ee1 100644 --- a/d2common/d2fileformats/d2ds1/floor_shadow_record.go +++ b/d2common/d2fileformats/d2ds1/floor_shadow_record.go @@ -4,6 +4,32 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" ) +const ( + prop1Bitmask = 0x000000FF + prop1Offset = 0 + prop1Length = 8 + + sequenceBitmask = 0x00003F00 + sequenceOffset = 8 + sequenceLength = 6 + + unknown1Bitmask = 0x000FC000 + unknown1Offset = 14 + unknown1Length = 6 + + styleBitmask = 0x03F00000 + styleOffset = 20 + styleLength = 6 + + unknown2Bitmask = 0x7C000000 + unknown2Offset = 26 + unknown2Length = 5 + + hiddenBitmask = 0x80000000 + hiddenOffset = 31 + hiddenLength = 1 +) + // FloorShadowRecord represents a floor or shadow record in a DS1 file. type FloorShadowRecord struct { Prop1 byte @@ -24,20 +50,20 @@ func (f *FloorShadowRecord) Hidden() bool { // Decode decodes floor-shadow record func (f *FloorShadowRecord) Decode(dw uint32) { - f.Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask - f.Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask - f.Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask - f.Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask - f.Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask - f.hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask + f.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) + f.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) + f.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) + f.Style = byte((dw & styleBitmask) >> styleOffset) + f.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset) + f.hidden = byte((dw & hiddenBitmask) >> hiddenOffset) } // Encode adds Floor's bits to stream writter given func (f *FloorShadowRecord) Encode(sw *d2datautils.StreamWriter) { - sw.PushBits32(uint32(f.Prop1), 8) - sw.PushBits32(uint32(f.Sequence), 6) - sw.PushBits32(uint32(f.Unknown1), 6) - sw.PushBits32(uint32(f.Style), 6) - sw.PushBits32(uint32(f.Unknown2), 5) - sw.PushBits32(uint32(f.hidden), 1) + sw.PushBits32(uint32(f.Prop1), prop1Length) + sw.PushBits32(uint32(f.Sequence), sequenceLength) + sw.PushBits32(uint32(f.Unknown1), unknown1Length) + sw.PushBits32(uint32(f.Style), styleLength) + sw.PushBits32(uint32(f.Unknown2), unknown2Length) + sw.PushBits32(uint32(f.hidden), hiddenLength) } diff --git a/d2common/d2fileformats/d2ds1/wall_record.go b/d2common/d2fileformats/d2ds1/wall_record.go index 855594ee..c25d082e 100644 --- a/d2common/d2fileformats/d2ds1/wall_record.go +++ b/d2common/d2fileformats/d2ds1/wall_record.go @@ -24,22 +24,22 @@ func (w *WallRecord) Hidden() bool { return w.hidden > 0 } -// Encode adds wall's record's bytes into stream writer given -func (w *WallRecord) Encode(sw *d2datautils.StreamWriter) { - sw.PushBits32(uint32(w.Prop1), 8) - sw.PushBits32(uint32(w.Sequence), 6) - sw.PushBits32(uint32(w.Unknown1), 6) - sw.PushBits32(uint32(w.Style), 6) - sw.PushBits32(uint32(w.Unknown2), 5) - sw.PushBits32(uint32(w.hidden), 1) -} - // Decode decodes wall record func (w *WallRecord) Decode(dw uint32) { - w.Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask - w.Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask - w.Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask - w.Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask - w.Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask - w.hidden = byte((dw & 0x80000000) >> 31) //nolint:gomnd // Bitmask + w.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset) + w.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset) + w.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset) + w.Style = byte((dw & styleBitmask) >> styleOffset) + w.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset) + w.hidden = byte((dw & hiddenBitmask) >> hiddenOffset) +} + +// Encode adds wall's record's bytes into stream writer given +func (w *WallRecord) Encode(sw *d2datautils.StreamWriter) { + sw.PushBits32(uint32(w.Prop1), prop1Length) + sw.PushBits32(uint32(w.Sequence), sequenceLength) + sw.PushBits32(uint32(w.Unknown1), unknown1Length) + sw.PushBits32(uint32(w.Style), styleLength) + sw.PushBits32(uint32(w.Unknown2), unknown2Length) + sw.PushBits32(uint32(w.hidden), hiddenLength) } From 215ac8cfc50a019367c4cf3a1cd1ff85d5fc83b5 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Sat, 6 Feb 2021 18:58:15 +0100 Subject: [PATCH 08/17] ds1: splited loading function --- d2common/d2fileformats/d2ds1/ds1.go | 180 ++++++++++++++++------------ 1 file changed, 101 insertions(+), 79 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index ed51f997..f7222d18 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -9,22 +9,21 @@ import ( ) 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 + 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 ) const ( @@ -34,6 +33,10 @@ const ( wallTypeBitmask = 0x000000FF ) +const ( + unknown1BytesCount = 8 +) + // DS1 represents the "stamp" data that is used to build up maps. type DS1 struct { Files []string // FilePtr table of file string pointers @@ -57,7 +60,6 @@ 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, @@ -71,74 +73,14 @@ func LoadDS1(fileData []byte) (*DS1, error) { var err error - ds1.Version, err = br.ReadInt32() + err = ds1.loadHeader(br) if err != nil { return nil, err } - 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 >= v3 { - // These files reference things that don't exist anymore :-? - 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, err := br.ReadByte() - if err != nil { - return nil, err - } - - if ch == 0 { - break - } - - ds1.Files[i] += string(ch) - } - } - } - if ds1.Version >= v9 && ds1.Version <= v13 { // Skipping two dwords because they are "meaningless"? - ds1.unknown1, err = br.ReadBytes(8) //nolint:gomnd // We don't know what's here + ds1.unknown1, err = br.ReadBytes(unknown1BytesCount) if err != nil { return nil, err } @@ -197,6 +139,86 @@ func LoadDS1(fileData []byte) (*DS1, error) { return ds1, nil } +func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { + var err error + + ds1.Version, err = br.ReadInt32() + if err != nil { + return err + } + + ds1.Width, err = br.ReadInt32() + if err != nil { + return err + } + + ds1.Height, err = br.ReadInt32() + if err != nil { + return err + } + + ds1.Width++ + ds1.Height++ + + if ds1.Version >= v8 { + ds1.Act, err = br.ReadInt32() + if err != nil { + return err + } + + ds1.Act = d2math.MinInt32(d2enum.ActsNumber, ds1.Act+1) + } + + if ds1.Version >= v10 { + ds1.SubstitutionType, err = br.ReadInt32() + if err != nil { + return err + } + + if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 { + ds1.NumberOfSubstitutionLayers = 1 + } + } + + err = ds1.loadFileList(br) + if err != nil { + return err + } + + return nil +} + +func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { + if ds1.Version >= v3 { + // These files reference things that don't exist anymore :-? + numberOfFiles, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... + if err != nil { + return err + } + + ds1.Files = make([]string, numberOfFiles) + + for i := 0; i < int(numberOfFiles); i++ { + ds1.Files[i] = "" + + for { + ch, err := br.ReadByte() + if err != nil { + return err + } + + if ch == 0 { + break + } + + ds1.Files[i] += string(ch) + } + } + } + + return nil +} + func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error { if ds1.Version < v2 { ds1.Objects = make([]Object, 0) From 20f0d8a3d5fe2789b0c49ec2f36fd41407b2e118 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Sat, 6 Feb 2021 19:10:15 +0100 Subject: [PATCH 09/17] removed some of nolint:govet --- d2common/d2fileformats/d2ds1/ds1.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index f7222d18..a039ea2f 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -191,7 +191,7 @@ func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error { func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error { if ds1.Version >= v3 { // These files reference things that don't exist anymore :-? - numberOfFiles, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... + numberOfFiles, err := br.ReadInt32() if err != nil { return err } @@ -379,17 +379,17 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error { } for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ { - numPaths, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... + numPaths, err := br.ReadInt32() // nolint:govet // I want to re-use this error variable if err != nil { return err } - npcX, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... + npcX, err := br.ReadInt32() if err != nil { return err } - npcY, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... + npcY, err := br.ReadInt32() if err != nil { return err } From 8e1ca1dd7fcf446b2cd30ec6fa6e585f67c7fb65 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Sat, 6 Feb 2021 20:43:04 +0100 Subject: [PATCH 10/17] stream writer test: added test for pushing bits --- d2common/d2datautils/stream_writer_test.go | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/d2common/d2datautils/stream_writer_test.go b/d2common/d2datautils/stream_writer_test.go index 1f7dd95c..cc199739 100644 --- a/d2common/d2datautils/stream_writer_test.go +++ b/d2common/d2datautils/stream_writer_test.go @@ -4,6 +4,62 @@ import ( "testing" ) +func TestStreamWriterBits(t *testing.T) { + sr := CreateStreamWriter() + data := []byte{221, 19} + + for _, i := range data { + sr.PushBits(i, bitsPerByte) + } + + output := sr.GetBytes() + for i, d := range data { + if output[i] != d { + t.Fatalf("sr.PushBits() pushed %X, but wrote %X instead", d, output[i]) + } + } +} + +func TestStreamWriterBits16(t *testing.T) { + sr := CreateStreamWriter() + data := []uint16{1024, 19} + + for _, i := range data { + sr.PushBits16(i, bitsPerByte*bytesPerint16) + } + + output := sr.GetBytes() + + for i, d := range data { + outputInt := uint16(output[bytesPerint16*i]) | uint16(output[bytesPerint16*i+1])<<8 + if outputInt != d { + t.Fatalf("sr.PushBits16() pushed %X, but wrote %X instead", d, output[i]) + } + } +} + +func TestStreamWriterBits32(t *testing.T) { + sr := CreateStreamWriter() + data := []uint32{19324, 87} + + for _, i := range data { + sr.PushBits32(i, bitsPerByte*bytesPerint32) + } + + output := sr.GetBytes() + + for i, d := range data { + outputInt := uint32(output[bytesPerint32*i]) | + uint32(output[bytesPerint32*i+1])<<8 | + uint32(output[bytesPerint32*i+2])<<16 | + uint32(output[bytesPerint32*i+3])<<24 + + if outputInt != d { + t.Fatalf("sr.PushBits32() pushed %X, but wrote %X instead", d, output[i]) + } + } +} + func TestStreamWriterByte(t *testing.T) { sr := CreateStreamWriter() data := []byte{0x12, 0x34, 0x56, 0x78} From 6d098de778dea8f6f9b865d28731f64d8a05bf1f Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Sun, 7 Feb 2021 18:34:10 +0100 Subject: [PATCH 11/17] d2dc6 refactor + unit test for it --- d2common/d2fileformats/d2dc6/dc6.go | 41 +++++++++++--- d2common/d2fileformats/d2dc6/dc6_test.go | 69 ++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 d2common/d2fileformats/d2dc6/dc6_test.go diff --git a/d2common/d2fileformats/d2dc6/dc6.go b/d2common/d2fileformats/d2dc6/dc6.go index e903f022..2c10f9e7 100644 --- a/d2common/d2fileformats/d2dc6/dc6.go +++ b/d2common/d2fileformats/d2dc6/dc6.go @@ -32,17 +32,42 @@ type DC6 struct { Frames []*DC6Frame // size is Directions*FramesPerDirection } -// Load uses restruct to read the binary dc6 data into structs then parses image data from the frame data. +// New creates a new, empty DC6 +func New() *DC6 { + result := &DC6{ + Version: 0, + Flags: 0, + Encoding: 0, + Termination: make([]byte, 4), + Directions: 0, + FramesPerDirection: 0, + FramePointers: make([]uint32, 0), + Frames: make([]*DC6Frame, 0), + } + + return result +} + +// Load loads a dc6 animation func Load(data []byte) (*DC6, error) { - r := d2datautils.CreateStreamReader(data) + d := New() + err := d.Unmarshal(data) + if err != nil { + return nil, err + } - var dc DC6 + return d, nil +} +// Unmarshal converts bite slice into DC6 structure +func (dc *DC6) Unmarshal(data []byte) error { var err error + r := d2datautils.CreateStreamReader(data) + err = dc.loadHeader(r) if err != nil { - return nil, err + return err } frameCount := int(dc.Directions * dc.FramesPerDirection) @@ -51,17 +76,17 @@ func Load(data []byte) (*DC6, error) { for i := 0; i < frameCount; i++ { dc.FramePointers[i], err = r.ReadUInt32() if err != nil { - return nil, err + return err } } dc.Frames = make([]*DC6Frame, frameCount) if err := dc.loadFrames(r); err != nil { - return nil, err + return err } - return &dc, nil + return nil } func (d *DC6) loadHeader(r *d2datautils.StreamReader) error { @@ -241,7 +266,7 @@ func (d *DC6) Clone() *DC6 { for i := range d.Frames { cloneFrame := *d.Frames[i] - clone.Frames = append(clone.Frames, &cloneFrame) + clone.Frames[i] = &cloneFrame } return &clone diff --git a/d2common/d2fileformats/d2dc6/dc6_test.go b/d2common/d2fileformats/d2dc6/dc6_test.go new file mode 100644 index 00000000..2c4a0e8e --- /dev/null +++ b/d2common/d2fileformats/d2dc6/dc6_test.go @@ -0,0 +1,69 @@ +package d2dc6 + +import ( + "testing" +) + +func TestDC6New(t *testing.T) { + dc6 := New() + + if dc6 == nil { + t.Error("d2dc6.New() method returned nil") + } +} + +func getExampleDC6() *DC6 { + exampleDC6 := &DC6{ + Version: 6, + Flags: 1, + Encoding: 0, + Termination: []byte{238, 238, 238, 238}, + Directions: 1, + FramesPerDirection: 1, + FramePointers: []uint32{56}, + Frames: []*DC6Frame{ + &DC6Frame{ + Flipped: 0, + Width: 32, + Height: 26, + OffsetX: 45, + OffsetY: 24, + Unknown: 0, + NextBlock: 50, + Length: 10, + FrameData: []byte{2, 23, 34, 128, 53, 64, 39, 43, 123, 12}, + Terminator: []byte{2, 8, 5}, + }, + }, + } + + return exampleDC6 +} + +func TestDC6Unmarshal(t *testing.T) { + exampleDC6 := getExampleDC6() + + data := exampleDC6.Marshal() + + extractedDC6, err := Load(data) + if err != nil { + t.Error(err) + } + + if exampleDC6.Version != extractedDC6.Version || + len(exampleDC6.Frames) != len(extractedDC6.Frames) || + exampleDC6.Frames[0].NextBlock != extractedDC6.Frames[0].NextBlock { + t.Fatal("encoded and decoded DC6 isn't the same") + } +} + +func TestDC6Clone(t *testing.T) { + exampleDC6 := getExampleDC6() + clonedDC6 := exampleDC6.Clone() + + if exampleDC6.Termination[0] != clonedDC6.Termination[0] || + len(exampleDC6.Frames) != len(clonedDC6.Frames) || + exampleDC6.Frames[0].NextBlock != clonedDC6.Frames[0].NextBlock { + t.Fatal("cloned dc6 isn't equal to orginal") + } +} From 622e54dfce679bccecb2b223d42e1d6ee5c09303 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Sun, 7 Feb 2021 18:40:16 +0100 Subject: [PATCH 12/17] dc6 refactor: lintfix --- d2common/d2fileformats/d2dc6/dc6.go | 15 ++++++++------- d2common/d2fileformats/d2dc6/dc6_test.go | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/d2common/d2fileformats/d2dc6/dc6.go b/d2common/d2fileformats/d2dc6/dc6.go index 2c10f9e7..0426f4f3 100644 --- a/d2common/d2fileformats/d2dc6/dc6.go +++ b/d2common/d2fileformats/d2dc6/dc6.go @@ -51,6 +51,7 @@ func New() *DC6 { // Load loads a dc6 animation func Load(data []byte) (*DC6, error) { d := New() + err := d.Unmarshal(data) if err != nil { return nil, err @@ -60,29 +61,29 @@ func Load(data []byte) (*DC6, error) { } // Unmarshal converts bite slice into DC6 structure -func (dc *DC6) Unmarshal(data []byte) error { +func (d *DC6) Unmarshal(data []byte) error { var err error r := d2datautils.CreateStreamReader(data) - err = dc.loadHeader(r) + err = d.loadHeader(r) if err != nil { return err } - frameCount := int(dc.Directions * dc.FramesPerDirection) + frameCount := int(d.Directions * d.FramesPerDirection) - dc.FramePointers = make([]uint32, frameCount) + d.FramePointers = make([]uint32, frameCount) for i := 0; i < frameCount; i++ { - dc.FramePointers[i], err = r.ReadUInt32() + d.FramePointers[i], err = r.ReadUInt32() if err != nil { return err } } - dc.Frames = make([]*DC6Frame, frameCount) + d.Frames = make([]*DC6Frame, frameCount) - if err := dc.loadFrames(r); err != nil { + if err := d.loadFrames(r); err != nil { return err } diff --git a/d2common/d2fileformats/d2dc6/dc6_test.go b/d2common/d2fileformats/d2dc6/dc6_test.go index 2c4a0e8e..3da98399 100644 --- a/d2common/d2fileformats/d2dc6/dc6_test.go +++ b/d2common/d2fileformats/d2dc6/dc6_test.go @@ -22,7 +22,7 @@ func getExampleDC6() *DC6 { FramesPerDirection: 1, FramePointers: []uint32{56}, Frames: []*DC6Frame{ - &DC6Frame{ + { Flipped: 0, Width: 32, Height: 26, @@ -64,6 +64,6 @@ func TestDC6Clone(t *testing.T) { if exampleDC6.Termination[0] != clonedDC6.Termination[0] || len(exampleDC6.Frames) != len(clonedDC6.Frames) || exampleDC6.Frames[0].NextBlock != clonedDC6.Frames[0].NextBlock { - t.Fatal("cloned dc6 isn't equal to orginal") + t.Fatal("cloned dc6 isn't equal to original") } } From 8a15c0b0741568a2b4b0d473b83de227bcfdb705 Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Wed, 10 Feb 2021 08:24:53 +0100 Subject: [PATCH 13/17] hotfix: cof encoder: coding weapon class --- d2common/d2fileformats/d2cof/cof.go | 11 +++++++++-- d2common/d2fileformats/d2cof/cof_layer.go | 13 ++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/d2common/d2fileformats/d2cof/cof.go b/d2common/d2fileformats/d2cof/cof.go index 5b119b47..3b47c4a3 100644 --- a/d2common/d2fileformats/d2cof/cof.go +++ b/d2common/d2fileformats/d2cof/cof.go @@ -121,7 +121,6 @@ func (c *COF) Unmarshal(fileData []byte) error { layer.Transparent = b[layerTransparent] > 0 layer.DrawEffect = d2enum.DrawEffect(b[layerDrawEffect]) - layer.weaponClassByte = b[layerWeaponClass:] layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll( string(b[layerWeaponClass:]), badCharacter, ""))) @@ -193,7 +192,15 @@ func (c *COF) Marshal() []byte { sw.PushBytes(byte(c.CofLayers[i].DrawEffect)) - sw.PushBytes(c.CofLayers[i].weaponClassByte...) + s := c.CofLayers[i].WeaponClass.String() + + for j := 0; j < 4; j++ { + if j < len(s) { + sw.PushBytes(s[j]) + } else { + sw.PushBytes(0) + } + } } for _, i := range c.AnimationFrames { diff --git a/d2common/d2fileformats/d2cof/cof_layer.go b/d2common/d2fileformats/d2cof/cof_layer.go index 2658e691..8a09f9c1 100644 --- a/d2common/d2fileformats/d2cof/cof_layer.go +++ b/d2common/d2fileformats/d2cof/cof_layer.go @@ -4,11 +4,10 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" // CofLayer is a structure that represents a single layer in a COF file. type CofLayer struct { - Type d2enum.CompositeType - Shadow byte - Selectable bool - Transparent bool - DrawEffect d2enum.DrawEffect - WeaponClass d2enum.WeaponClass - weaponClassByte []byte + Type d2enum.CompositeType + Shadow byte + Selectable bool + Transparent bool + DrawEffect d2enum.DrawEffect + WeaponClass d2enum.WeaponClass } From 10103530716f2a0293f990feca3358303327999f Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Wed, 10 Feb 2021 12:35:35 +0100 Subject: [PATCH 14/17] hotfix: d2cof encoder: removed magic number (len of weapon class) --- d2common/d2fileformats/d2cof/cof.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/d2common/d2fileformats/d2cof/cof.go b/d2common/d2fileformats/d2cof/cof.go index 3b47c4a3..fa9b1002 100644 --- a/d2common/d2fileformats/d2cof/cof.go +++ b/d2common/d2fileformats/d2cof/cof.go @@ -30,6 +30,10 @@ const ( layerWeaponClass ) +const ( + layerWeaponClassLength = 4 +) + const ( badCharacter = string(byte(0)) ) @@ -192,11 +196,11 @@ func (c *COF) Marshal() []byte { sw.PushBytes(byte(c.CofLayers[i].DrawEffect)) - s := c.CofLayers[i].WeaponClass.String() + weaponClassString := c.CofLayers[i].WeaponClass.String() - for j := 0; j < 4; j++ { - if j < len(s) { - sw.PushBytes(s[j]) + for letter := 0; letter < layerWeaponClassLength; letter++ { + if letter < len(weaponClassString) { + sw.PushBytes(weaponClassString[letter]) } else { sw.PushBytes(0) } From 7d0eeb0fd3a0bbb6a4cfda0cd05fc3bb9f537b0f Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Wed, 10 Feb 2021 12:59:07 +0100 Subject: [PATCH 15/17] hotfix: d2cof encoder: changed way of pushing weapon class --- d2common/d2fileformats/d2cof/cof.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/d2common/d2fileformats/d2cof/cof.go b/d2common/d2fileformats/d2cof/cof.go index fa9b1002..ceb9ab11 100644 --- a/d2common/d2fileformats/d2cof/cof.go +++ b/d2common/d2fileformats/d2cof/cof.go @@ -30,10 +30,6 @@ const ( layerWeaponClass ) -const ( - layerWeaponClassLength = 4 -) - const ( badCharacter = string(byte(0)) ) @@ -196,15 +192,22 @@ func (c *COF) Marshal() []byte { sw.PushBytes(byte(c.CofLayers[i].DrawEffect)) - weaponClassString := c.CofLayers[i].WeaponClass.String() + const ( + maxCodeLength = 3 // we assume item codes to look like 'hax' or 'kit' + terminator = 0 + ) - for letter := 0; letter < layerWeaponClassLength; letter++ { - if letter < len(weaponClassString) { - sw.PushBytes(weaponClassString[letter]) - } else { - sw.PushBytes(0) + weaponCode := c.CofLayers[i].WeaponClass.String() + + for idx, letter := range weaponCode { + if idx > maxCodeLength { + break } + + sw.PushBytes(byte(letter)) } + + sw.PushBytes(terminator) } for _, i := range c.AnimationFrames { From 8a087dba6cdf8ea2cfeaf4f43f5c6fd687cb0f3e Mon Sep 17 00:00:00 2001 From: "M. Sz" Date: Wed, 10 Feb 2021 14:00:03 +0100 Subject: [PATCH 16/17] stream writer and stream writer test: - fixed typo - cut PushBits... methods - removed magic number --- d2common/d2datautils/stream_writer.go | 29 ++++++---------------- d2common/d2datautils/stream_writer_test.go | 5 +++- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/d2common/d2datautils/stream_writer.go b/d2common/d2datautils/stream_writer.go index 98de3746..57a2a90e 100644 --- a/d2common/d2datautils/stream_writer.go +++ b/d2common/d2datautils/stream_writer.go @@ -34,7 +34,7 @@ func (v *StreamWriter) PushBytes(b ...byte) { } // PushBit pushes single bit into stream -// WARNING: if you'll use PushBit, offset'll be less then 7, and if you'll +// WARNING: if you'll use PushBit, offset'll be less than 8, and if you'll // use another Push... method, bits'll not be pushed func (v *StreamWriter) PushBit(b bool) { if b { @@ -54,17 +54,12 @@ func (v *StreamWriter) PushBit(b bool) { // PushBits pushes bits (with max range 8) func (v *StreamWriter) PushBits(b byte, bits int) { if bits > bitsPerByte { - log.Print("input bits number must be less (or equal) then 8") + log.Print("input bits number must be less (or equal) than 8") } val := b for i := 0; i < bits; i++ { - if val&1 == 1 { - v.PushBit(true) - } else { - v.PushBit(false) - } - + v.PushBit(val&1 == 1) val >>= 1 } } @@ -72,17 +67,12 @@ func (v *StreamWriter) PushBits(b byte, bits int) { // PushBits16 pushes bits (with max range 16) func (v *StreamWriter) PushBits16(b uint16, bits int) { if bits > bitsPerByte*bytesPerint16 { - log.Print("input bits number must be less (or equal) then 16") + log.Print("input bits number must be less (or equal) than 16") } val := b for i := 0; i < bits; i++ { - if val&1 == 1 { - v.PushBit(true) - } else { - v.PushBit(false) - } - + v.PushBit(val&1 == 1) val >>= 1 } } @@ -90,17 +80,12 @@ func (v *StreamWriter) PushBits16(b uint16, bits int) { // PushBits32 pushes bits (with max range 32) func (v *StreamWriter) PushBits32(b uint32, bits int) { if bits > bitsPerByte*bytesPerint32 { - log.Print("input bits number must be less (or equal) then 32") + log.Print("input bits number must be less (or equal) than 32") } val := b for i := 0; i < bits; i++ { - if val&1 == 1 { - v.PushBit(true) - } else { - v.PushBit(false) - } - + v.PushBit(val&1 == 1) val >>= 1 } } diff --git a/d2common/d2datautils/stream_writer_test.go b/d2common/d2datautils/stream_writer_test.go index cc199739..68c22cda 100644 --- a/d2common/d2datautils/stream_writer_test.go +++ b/d2common/d2datautils/stream_writer_test.go @@ -31,7 +31,9 @@ func TestStreamWriterBits16(t *testing.T) { output := sr.GetBytes() for i, d := range data { - outputInt := uint16(output[bytesPerint16*i]) | uint16(output[bytesPerint16*i+1])<<8 + // nolint:gomnd // offset in byte slice; bit shifts for uint16 + outputInt := uint16(output[bytesPerint16*i]) | + uint16(output[bytesPerint16*i+1])<<8 if outputInt != d { t.Fatalf("sr.PushBits16() pushed %X, but wrote %X instead", d, output[i]) } @@ -49,6 +51,7 @@ func TestStreamWriterBits32(t *testing.T) { output := sr.GetBytes() for i, d := range data { + // nolint:gomnd // offset in byte slice; bit shifts for uint32 outputInt := uint32(output[bytesPerint32*i]) | uint32(output[bytesPerint32*i+1])<<8 | uint32(output[bytesPerint32*i+2])<<16 | From 025a17250075bde65d71e973fd36e9e2e69ec9b0 Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Wed, 10 Feb 2021 19:49:21 +0100 Subject: [PATCH 17/17] fixed lint errors in stream_writter --- d2common/d2datautils/stream_writer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/d2common/d2datautils/stream_writer.go b/d2common/d2datautils/stream_writer.go index 57a2a90e..d2939af9 100644 --- a/d2common/d2datautils/stream_writer.go +++ b/d2common/d2datautils/stream_writer.go @@ -58,6 +58,7 @@ func (v *StreamWriter) PushBits(b byte, bits int) { } val := b + for i := 0; i < bits; i++ { v.PushBit(val&1 == 1) val >>= 1 @@ -71,6 +72,7 @@ func (v *StreamWriter) PushBits16(b uint16, bits int) { } val := b + for i := 0; i < bits; i++ { v.PushBit(val&1 == 1) val >>= 1 @@ -84,6 +86,7 @@ func (v *StreamWriter) PushBits32(b uint32, bits int) { } val := b + for i := 0; i < bits; i++ { v.PushBit(val&1 == 1) val >>= 1