diff --git a/d2common/d2datautils/stream_writer.go b/d2common/d2datautils/stream_writer.go
index 432fcdbb..d2939af9 100644
--- a/d2common/d2datautils/stream_writer.go
+++ b/d2common/d2datautils/stream_writer.go
@@ -1,10 +1,15 @@
 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 {
-	data *bytes.Buffer
+	data      *bytes.Buffer
+	bitOffset int
+	bitCache  byte
 }
 
 // CreateStreamWriter creates a new StreamWriter instance
@@ -28,6 +33,66 @@ func (v *StreamWriter) PushBytes(b ...byte) {
 	}
 }
 
+// PushBit pushes single bit into stream
+// 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 {
+		v.bitCache |= (1 << v.bitOffset)
+	}
+	v.bitOffset++
+
+	if v.bitOffset != bitsPerByte {
+		return
+	}
+
+	v.PushBytes(v.bitCache)
+	v.bitCache = 0
+	v.bitOffset = 0
+}
+
+// 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) than 8")
+	}
+
+	val := b
+
+	for i := 0; i < bits; i++ {
+		v.PushBit(val&1 == 1)
+		val >>= 1
+	}
+}
+
+// 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) than 16")
+	}
+
+	val := b
+
+	for i := 0; i < bits; i++ {
+		v.PushBit(val&1 == 1)
+		val >>= 1
+	}
+}
+
+// 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) than 32")
+	}
+
+	val := b
+
+	for i := 0; i < bits; i++ {
+		v.PushBit(val&1 == 1)
+		val >>= 1
+	}
+}
+
 // PushInt16 writes a int16 word to the stream
 func (v *StreamWriter) PushInt16(val int16) {
 	v.PushUint16(uint16(val))
diff --git a/d2common/d2datautils/stream_writer_test.go b/d2common/d2datautils/stream_writer_test.go
index 1f7dd95c..68c22cda 100644
--- a/d2common/d2datautils/stream_writer_test.go
+++ b/d2common/d2datautils/stream_writer_test.go
@@ -4,6 +4,65 @@ 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 {
+		// 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])
+		}
+	}
+}
+
+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 {
+		// 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 |
+			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}
diff --git a/d2common/d2fileformats/d2cof/cof.go b/d2common/d2fileformats/d2cof/cof.go
index 5b119b47..ceb9ab11 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,22 @@ func (c *COF) Marshal() []byte {
 
 		sw.PushBytes(byte(c.CofLayers[i].DrawEffect))
 
-		sw.PushBytes(c.CofLayers[i].weaponClassByte...)
+		const (
+			maxCodeLength = 3 // we assume item codes to look like 'hax' or 'kit'
+			terminator    = 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 {
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
 }
diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go
index a27b8e7e..a039ea2f 100644
--- a/d2common/d2fileformats/d2ds1/ds1.go
+++ b/d2common/d2fileformats/d2ds1/ds1.go
@@ -9,22 +9,32 @@ 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 (
+	wallZeroBitmask = 0xFFFFFF00
+	wallZeroOffset  = 8
+
+	wallTypeBitmask = 0x000000FF
+)
+
+const (
+	unknown1BytesCount = 8
 )
 
 // DS1 represents the "stamp" data that is used to build up maps.
@@ -43,10 +53,13 @@ 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
-//nolint:funlen,gocognit,gocyclo // will refactor later
 func LoadDS1(fileData []byte) (*DS1, error) {
 	ds1 := &DS1{
 		Act:                        1,
@@ -60,74 +73,17 @@ 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"?
-		br.SkipBytes(8) //nolint:gomnd // We don't know what's here
+		ds1.unknown1, err = br.ReadBytes(unknown1BytesCount)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	if ds1.Version >= v4 {
@@ -146,7 +102,7 @@ func LoadDS1(fileData []byte) (*DS1, error) {
 		}
 	}
 
-	layerStream := ds1.setupStreamLayerTypes()
+	ds1.layerStreamTypes = ds1.setupStreamLayerTypes()
 
 	ds1.Tiles = make([][]TileRecord, ds1.Height)
 
@@ -160,7 +116,7 @@ func LoadDS1(fileData []byte) (*DS1, error) {
 		}
 	}
 
-	err = ds1.loadLayerStreams(br, layerStream)
+	err = ds1.loadLayerStreams(br)
 	if err != nil {
 		return nil, err
 	}
@@ -183,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()
+		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)
@@ -245,7 +281,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()
@@ -340,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
 		}
@@ -360,6 +399,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 +459,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 +468,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++ {
@@ -440,16 +481,11 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
 				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) > 0   //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)) {
@@ -458,22 +494,12 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
 					}
 
 					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].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) > 0   //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) > 0   //nolint:gomnd // Bitmask
+					ds1.Tiles[y][x].Shadows[0].Decode(dw)
 				case d2enum.LayerStreamSubstitute:
 					ds1.Tiles[y][x].Substitutions[0].Unknown = dw
 				}
@@ -483,3 +509,134 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
 
 	return err
 }
+
+// Marshal encodes ds1 back to byte slice
+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
+	ds1.encodeLayers(sw)
+
+	// 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 - 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) & wallZeroBitmask) << wallZeroOffset
+
+					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)))
+
+	// 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))
+			}
+		}
+	}
+}
diff --git a/d2common/d2fileformats/d2ds1/floor_shadow_record.go b/d2common/d2fileformats/d2ds1/floor_shadow_record.go
index 0b569457..dccf7ee1 100644
--- a/d2common/d2fileformats/d2ds1/floor_shadow_record.go
+++ b/d2common/d2fileformats/d2ds1/floor_shadow_record.go
@@ -1,5 +1,35 @@
 package d2ds1
 
+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
@@ -7,8 +37,33 @@ 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
+}
+
+// Decode decodes floor-shadow record
+func (f *FloorShadowRecord) Decode(dw uint32) {
+	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), 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 5a6571fa..c25d082e 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 {
@@ -11,7 +14,32 @@ 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
+}
+
+// Decode decodes wall record
+func (w *WallRecord) Decode(dw uint32) {
+	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)
+}
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])
 			}
 		}