CVE-2021-33806: Deserialization of Untrusted Data (CWE-502)
value: The BDew BdLib library before 1.16.1.7 for Minecraft allows remote code execution because it deserializes untrusted data in ObjectInputStream.readObject as part of its use of Java serialization.
CWE description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
CVSS v3 base score: 9.8
Published: 2021-06-03T12:15Z
Repository: https://github.com/bdew-minecraft/bdlib
Language: Scala
Files changed: build.properties, src/main/scala/net/bdew/lib/multiblock/data/OutputConfig.scala, src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluid.scala, src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluidSlots.scala, src/main/scala/net/bdew/lib/multiblock/data/OutputConfigItems.scala, src/main/scala/net/bdew/lib/multiblock/data/OutputConfigPower.scala, src/main/scala/net/bdew/lib/multiblock/gui/WidgetRSConfig.scala, src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfg.scala, src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgRSMode.scala, src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgSlot.scala, src/main/scala/net/bdew/lib/multiblock/network/MultiblockNetHandler.scala, src/main/scala/net/bdew/lib/network/BaseMessage.scala, src/main/scala/net/bdew/lib/network/NetChannel.scala
Commit date: 2021-05-31 17:53:01 +0300
Commit message: reworked custom network code to not use java serialization

Diff: diff --git a/build.properties b/build.properties
index 6eceeaf..35791cf 100644
--- a/build.properties
+++ b/build.properties
@@ -1,7 +1,7 @@
 mod.id=bdlib
-mod.version=1.15.1
+mod.version=1.16.0
 curseforge.id=70496
 minecraft.version=1.16.5
 forge.version=36.1.0
 scala.version=2.13.5
 slp.version=2.13.5-build-1
\ No newline at end of file
diff --git a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfig.scala b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfig.scala
index 79d2d15..6b8b66e 100644
--- a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfig.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfig.scala
@@ -1,23 +1,23 @@
 package net.bdew.lib.multiblock.data
 
-import net.bdew.lib.multiblock.network.MsgOutputCfgPayload
+import net.bdew.lib.multiblock.network.MsgOutputCfg
 import net.minecraft.nbt.CompoundNBT
 import sun.reflect.generics.reflectiveObjects.NotImplementedException
 
 abstract class OutputConfig {
   def id: String
   def read(t: CompoundNBT): Unit
   def write(t: CompoundNBT): Unit
-  def handleConfigPacket(m: MsgOutputCfgPayload): Unit
+  def handleConfigPacket(m: MsgOutputCfg): Unit
 }
 
 class OutputConfigInvalid extends OutputConfig {
   override val id: String = "invalid"
   def read(t: CompoundNBT): Unit = {}
   def write(t: CompoundNBT): Unit = throw new NotImplementedException
-  def handleConfigPacket(m: MsgOutputCfgPayload): Unit = throw new NotImplementedException
+  def handleConfigPacket(m: MsgOutputCfg): Unit = throw new NotImplementedException
 }
 
 object OutputConfigManager {
   var loaders = Map.empty[String, () => OutputConfig]
   def register(id: String, loader: () => OutputConfig): Unit = loaders += id -> loader
diff --git a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluid.scala b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluid.scala
index dc06820..63c65d5 100644
--- a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluid.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluid.scala
@@ -1,10 +1,10 @@
 package net.bdew.lib.multiblock.data
 
 import net.bdew.lib.PimpVanilla._
 import net.bdew.lib.misc.RSMode
-import net.bdew.lib.multiblock.network.{MsgOutputCfgPayload, MsgOutputCfgRSMode}
+import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgRSMode}
 import net.minecraft.nbt.CompoundNBT
 
 import scala.collection.mutable
 
 class OutputConfigFluid extends OutputConfig with OutputConfigRSControllable {
@@ -31,10 +31,10 @@ class OutputConfigFluid extends OutputConfig with OutputConfigRSControllable {
   def write(t: CompoundNBT): Unit = {
     t.setListVals("values", values)
     t.putInt("rsMode", rsMode.id)
   }
 
-  def handleConfigPacket(m: MsgOutputCfgPayload): Unit = m match {
-    case MsgOutputCfgRSMode(r) => rsMode = r
+  def handleConfigPacket(m: MsgOutputCfg): Unit = m match {
+    case MsgOutputCfgRSMode(_, r) => rsMode = r
     case _ => sys.error("Invalid output config packet %s to config %s".format(m, this))
   }
 }
diff --git a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluidSlots.scala b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluidSlots.scala
index 5de70cc..c603787 100644
--- a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluidSlots.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigFluidSlots.scala
@@ -1,8 +1,8 @@
 package net.bdew.lib.multiblock.data
 
-import net.bdew.lib.multiblock.network.{MsgOutputCfgPayload, MsgOutputCfgSlot}
+import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgSlot}
 import net.minecraft.nbt.CompoundNBT
 
 trait OutputConfigSlots {
   val slotsDef: SlotSet
   var slot: slotsDef.Slot = slotsDef.default
@@ -16,10 +16,10 @@ class OutputConfigFluidSlots(val slotsDef: SlotSet) extends OutputConfigFluid wi
   }
   override def write(t: CompoundNBT): Unit = {
     super.write(t)
     t.putString("slot", slot.id)
   }
-  override def handleConfigPacket(m: MsgOutputCfgPayload): Unit = m match {
-    case MsgOutputCfgSlot(id) => slot = slotsDef.get(id)
+  override def handleConfigPacket(m: MsgOutputCfg): Unit = m match {
+    case MsgOutputCfgSlot(_, id) => slot = slotsDef.get(id)
     case _ => super.handleConfigPacket(m)
   }
 }
diff --git a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigItems.scala b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigItems.scala
index 26c7106..6f8963f 100644
--- a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigItems.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigItems.scala
@@ -1,9 +1,9 @@
 package net.bdew.lib.multiblock.data
 
 import net.bdew.lib.misc.RSMode
-import net.bdew.lib.multiblock.network.{MsgOutputCfgPayload, MsgOutputCfgRSMode}
+import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgRSMode}
 import net.minecraft.nbt.CompoundNBT
 
 class OutputConfigItems extends OutputConfig with OutputConfigRSControllable {
   override def id = "items"
   var rsMode: RSMode.Value = RSMode.ALWAYS
@@ -14,11 +14,11 @@ class OutputConfigItems extends OutputConfig with OutputConfigRSControllable {
 
   def write(t: CompoundNBT): Unit = {
     t.putInt("rsMode", rsMode.id)
   }
 
-  def handleConfigPacket(m: MsgOutputCfgPayload): Unit = m match {
-    case MsgOutputCfgRSMode(r) => rsMode = r
+  def handleConfigPacket(m: MsgOutputCfg): Unit = m match {
+    case MsgOutputCfgRSMode(_, r) => rsMode = r
     case _ => sys.error("Invalid output config packet %s to config %s".format(m, this))
   }
 
 }
diff --git a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigPower.scala b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigPower.scala
index 2479494..e002794 100644
--- a/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigPower.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/data/OutputConfigPower.scala
@@ -1,9 +1,9 @@
 package net.bdew.lib.multiblock.data
 
 import net.bdew.lib.misc.RSMode
-import net.bdew.lib.multiblock.network.{MsgOutputCfgPayload, MsgOutputCfgRSMode}
+import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgRSMode}
 import net.minecraft.nbt.CompoundNBT
 
 class OutputConfigPower(var unit: String = "fe") extends OutputConfig with OutputConfigRSControllable {
   override val id = "power"
   var avg = 0.0
@@ -25,10 +25,10 @@ class OutputConfigPower(var unit: String = "fe") extends OutputConfig with Outpu
     t.putDouble("avg", avg)
     t.putInt("rsMode", rsMode.id)
     t.putString("unit", unit)
   }
 
-  def handleConfigPacket(m: MsgOutputCfgPayload): Unit = m match {
-    case MsgOutputCfgRSMode(r) => rsMode = r
+  def handleConfigPacket(m: MsgOutputCfg): Unit = m match {
+    case MsgOutputCfgRSMode(_, r) => rsMode = r
     case _ => sys.error("Invalid output config packet %s to config %s".format(m, this))
   }
 }
diff --git a/src/main/scala/net/bdew/lib/multiblock/gui/WidgetRSConfig.scala b/src/main/scala/net/bdew/lib/multiblock/gui/WidgetRSConfig.scala
index ff0e668..15feada 100644
--- a/src/main/scala/net/bdew/lib/multiblock/gui/WidgetRSConfig.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/gui/WidgetRSConfig.scala
@@ -4,11 +4,11 @@ import net.bdew.lib.Text
 import net.bdew.lib.gui.widgets.{WidgetButtonIcon, WidgetSubContainer}
 import net.bdew.lib.gui.{Point, Rect, Texture}
 import net.bdew.lib.misc.RSMode
 import net.bdew.lib.multiblock.data.OutputConfigRSControllable
 import net.bdew.lib.multiblock.interact.CIOutputFaces
-import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgRSMode, MultiblockNetHandler}
+import net.bdew.lib.multiblock.network.{MsgOutputCfgRSMode, MultiblockNetHandler}
 import net.minecraft.util.text.ITextComponent
 
 import java.util.Locale
 
 class WidgetRSConfig(te: CIOutputFaces, output: Int, p: Point) extends WidgetSubContainer(new Rect(p, 16, 16)) {
@@ -32,8 +32,8 @@ class WidgetRSConfig(te: CIOutputFaces, output: Int, p: Point) extends WidgetSub
     RSMode.RS_OFF -> RSMode.NEVER,
     RSMode.NEVER -> RSMode.ALWAYS
   )
 
   def clicked(b: WidgetButtonIcon): Unit = {
-    MultiblockNetHandler.sendToServer(MsgOutputCfg(output, MsgOutputCfgRSMode(next(cfg.rsMode))))
+    MultiblockNetHandler.sendToServer(MsgOutputCfgRSMode(output, next(cfg.rsMode)))
   }
 }
diff --git a/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfg.scala b/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfg.scala
index e79129a..0a78fcc 100644
--- a/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfg.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfg.scala
@@ -1,5 +1,5 @@
 package net.bdew.lib.multiblock.network
 
-trait MsgOutputCfgPayload extends Serializable
-
-case class MsgOutputCfg(output: Int, payload: MsgOutputCfgPayload) extends MultiblockNetHandler.Message
+trait MsgOutputCfg {
+  def output: Int
+}
diff --git a/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgRSMode.scala b/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgRSMode.scala
index 0cef5e4..b5e62b1 100644
--- a/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgRSMode.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgRSMode.scala
@@ -1,5 +1,16 @@
 package net.bdew.lib.multiblock.network
 
 import net.bdew.lib.misc.RSMode
+import net.minecraft.network.PacketBuffer
 
-case class MsgOutputCfgRSMode(rsMode: RSMode.Value) extends MsgOutputCfgPayload
+case class MsgOutputCfgRSMode(output: Int, rsMode: RSMode.Value) extends MultiblockNetHandler.Message with MsgOutputCfg
+
+object CodecOutputCfgRSMode extends MultiblockNetHandler.Codec[MsgOutputCfgRSMode] {
+  override def encodeMsg(m: MsgOutputCfgRSMode, p: PacketBuffer): Unit = {
+    p.writeVarInt(m.output)
+    p.writeByte(m.rsMode.id)
+  }
+
+  override def decodeMsg(p: PacketBuffer): MsgOutputCfgRSMode =
+    MsgOutputCfgRSMode(p.readVarInt(), RSMode(p.readByte()))
+}
diff --git a/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgSlot.scala b/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgSlot.scala
index 0545dce..2f79191 100644
--- a/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgSlot.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/network/MsgOutputCfgSlot.scala
@@ -1,3 +1,16 @@
 package net.bdew.lib.multiblock.network
 
-case class MsgOutputCfgSlot(slot: String) extends MsgOutputCfgPayload
+import net.minecraft.network.PacketBuffer
+
+case class MsgOutputCfgSlot(output: Int, slot: String) extends MultiblockNetHandler.Message with MsgOutputCfg
+
+object CodecOutputCfgSlot extends MultiblockNetHandler.Codec[MsgOutputCfgSlot] {
+  override def encodeMsg(m: MsgOutputCfgSlot, p: PacketBuffer): Unit = {
+    p.writeVarInt(m.output)
+    p.writeUtf(m.slot)
+  }
+
+  override def decodeMsg(p: PacketBuffer): MsgOutputCfgSlot = {
+    MsgOutputCfgSlot(p.readVarInt(), p.readUtf(100))
+  }
+}
diff --git a/src/main/scala/net/bdew/lib/multiblock/network/MultiblockNetHandler.scala b/src/main/scala/net/bdew/lib/multiblock/network/MultiblockNetHandler.scala
index a1d48f0..1787b0f 100644
--- a/src/main/scala/net/bdew/lib/multiblock/network/MultiblockNetHandler.scala
+++ b/src/main/scala/net/bdew/lib/multiblock/network/MultiblockNetHandler.scala
@@ -1,11 +1,19 @@
 package net.bdew.lib.multiblock.network
 
 import net.bdew.lib.multiblock.interact.ContainerOutputFaces
 import net.bdew.lib.network.NetChannel
 
-object MultiblockNetHandler extends NetChannel("multiblock", "1") {
-  regServerContainerHandler(1, classOf[MsgOutputCfg], classOf[ContainerOutputFaces]) { (msg, cont, _) =>
-    cont.te.outputConfig(msg.output).handleConfigPacket(msg.payload)
+object MultiblockNetHandler extends NetChannel("multiblock", "2") {
+  private def forwardConfigMsg(m: MsgOutputCfg, cont: ContainerOutputFaces): Unit = {
+    cont.te.outputConfig(m.output).handleConfigPacket(m)
     cont.te.outputConfig.updated()
   }
+
+  regServerContainerHandler(1, CodecOutputCfgRSMode, classOf[ContainerOutputFaces]) { (msg, cont, _) =>
+    forwardConfigMsg(msg, cont)
+  }
+
+  regServerContainerHandler(2, CodecOutputCfgSlot, classOf[ContainerOutputFaces]) { (msg, cont, _) =>
+    forwardConfigMsg(msg, cont)
+  }
 }
diff --git a/src/main/scala/net/bdew/lib/network/BaseMessage.scala b/src/main/scala/net/bdew/lib/network/BaseMessage.scala
index a867735..94cfa8c 100644
--- a/src/main/scala/net/bdew/lib/network/BaseMessage.scala
+++ b/src/main/scala/net/bdew/lib/network/BaseMessage.scala
@@ -1,3 +1,3 @@
 package net.bdew.lib.network
 
-abstract class BaseMessage[CH <: NetChannel] extends Serializable
+abstract class BaseMessage[CH <: NetChannel]
diff --git a/src/main/scala/net/bdew/lib/network/CompoundNBTSerialize.scala b/src/main/scala/net/bdew/lib/network/CompoundNBTSerialize.scala
deleted file mode 100644
index f417306..0000000
--- a/src/main/scala/net/bdew/lib/network/CompoundNBTSerialize.scala
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.bdew.lib.network
-
-import net.minecraft.nbt.CompoundNBT
-
-import java.io.{ObjectInputStream, ObjectOutputStream}
-
-class CompoundNBTSerialize(var tag: CompoundNBT = new CompoundNBT) extends Serializable {
-  private def writeObject(out: ObjectOutputStream): Unit = {
-    out.writeObject(NBTHelper.toBytes(tag))
-  }
-
-  private def readObject(in: ObjectInputStream): Unit = {
-    tag = NBTHelper.fromBytes(in.readObject().asInstanceOf[Array[Byte]])
-  }
-}
-
-object CompoundNBTSerialize {
-
-  import scala.language.implicitConversions
-
-  implicit def ser2content(v: CompoundNBTSerialize): CompoundNBT = v.tag
-  implicit def content2ser(v: CompoundNBT): CompoundNBTSerialize = new CompoundNBTSerialize(v)
-}
diff --git a/src/main/scala/net/bdew/lib/network/NetChannel.scala b/src/main/scala/net/bdew/lib/network/NetChannel.scala
index 90a70c9..a5a8a6e 100644
--- a/src/main/scala/net/bdew/lib/network/NetChannel.scala
+++ b/src/main/scala/net/bdew/lib/network/NetChannel.scala
@@ -1,55 +1,50 @@
 package net.bdew.lib.network
 
-import io.netty.buffer.{ByteBufInputStream, ByteBufOutputStream}
 import net.bdew.lib.{BdLib, Misc}
 import net.minecraft.entity.player.ServerPlayerEntity
 import net.minecraft.inventory.container.Container
 import net.minecraft.network.PacketBuffer
 import net.minecraft.util.{RegistryKey, ResourceLocation}
 import net.minecraft.world.World
 import net.minecraftforge.fml.network.simple.SimpleChannel
 import net.minecraftforge.fml.network.{NetworkDirection, NetworkEvent, NetworkRegistry, PacketDistributor}
 
-import java.io.{ObjectInputStream, ObjectOutputStream}
 import java.util.Optional
+import scala.reflect.ClassTag
 
 class NetChannel(val name: String, val version: String) {
   val modId: String = Misc.getActiveModId
   val id = new ResourceLocation(modId, name)
 
   val channel: SimpleChannel = NetworkRegistry.newSimpleChannel(id, () => version, version.equals, version.equals)
 
   type Message = BaseMessage[this.type]
 
-  private def encodeMsg[M <: Message](m: Message, p: PacketBuffer): Unit = {
-    val writer = new ObjectOutputStream(new ByteBufOutputStream(p))
-    writer.writeObject(m)
-  }
-
-  private def decodeMsg[M <: Message](p: PacketBuffer): M = {
-    val reader = new ObjectInputStream(new ByteBufInputStream(p))
-    reader.readObject().asInstanceOf[M]
+  abstract class Codec[M <: Message : ClassTag] {
+    val cls: Class[M] = implicitly[ClassTag[M]].runtimeClass.asInstanceOf[Class[M]]
+    def encodeMsg(m: M, p: PacketBuffer): Unit
+    def decodeMsg(p: PacketBuffer): M
   }
 
   def init(): Unit = {
     BdLib.logInfo("Initialized network channel '%s' for mod '%s'", name, modId)
   }
 
-  def regServerHandler[M <: Message](id: Int, cls: Class[M])(handler: (M, NetworkEvent.Context) => Unit): Unit = {
-    channel.registerMessage[M](id, cls, (m, p) => encodeMsg[M](m, p), m => decodeMsg(m),
+  def regServerHandler[M <: Message](id: Int, codec: Codec[M])(handler: (M, NetworkEvent.Context) => Unit): Unit = {
+    channel.registerMessage[M](id, codec.cls, codec.encodeMsg, codec.decodeMsg,
       (m, cs) => handler(m, cs.get()), Optional.of(NetworkDirection.PLAY_TO_SERVER))
   }
 
-  def regServerContainerHandler[M <: Message, C <: Container](id: Int, cls: Class[M], cont: Class[C])(handler: (M, C, NetworkEvent.Context) => Unit): Unit = {
-    regServerHandler(id, cls) { (msg, ctx) =>
+  def regServerContainerHandler[M <: Message, C <: Container](id: Int, codec: Codec[M], cont: Class[C])(handler: (M, C, NetworkEvent.Context) => Unit): Unit = {
+    regServerHandler(id, codec) { (msg, ctx) =>
       Misc.asInstanceOpt(ctx.getSender.containerMenu, cont) foreach (cont => handler(msg, cont, ctx))
     }
   }
 
-  def regClientHandler[M <: Message](id: Int, cls: Class[M])(handler: M => Unit): Unit = {
-    channel.registerMessage[M](id, cls, (m, p) => encodeMsg[M](m, p), m => decodeMsg(m),
+  def regClientHandler[M <: Message](id: Int, codec: Codec[M])(handler: M => Unit): Unit = {
+    channel.registerMessage[M](id, codec.cls, codec.encodeMsg, codec.decodeMsg,
       (m, _) => handler(m), Optional.of(NetworkDirection.PLAY_TO_CLIENT))
   }
 
   def sendToAll(message: Message): Unit = {
     channel.send(PacketDistributor.ALL.noArg(), message)

Vulnerable code: mod.version=1.15.1
import net.bdew.lib.multiblock.network.MsgOutputCfgPayload
  def handleConfigPacket(m: MsgOutputCfgPayload): Unit
  def handleConfigPacket(m: MsgOutputCfgPayload): Unit = throw new NotImplementedException
import net.bdew.lib.multiblock.network.{MsgOutputCfgPayload, MsgOutputCfgRSMode}
  def handleConfigPacket(m: MsgOutputCfgPayload): Unit = m match {
    case MsgOutputCfgRSMode(r) => rsMode = r
import net.bdew.lib.multiblock.network.{MsgOutputCfgPayload, MsgOutputCfgSlot}
  override def handleConfigPacket(m: MsgOutputCfgPayload): Unit = m match {
    case MsgOutputCfgSlot(id) => slot = slotsDef.get(id)
import net.bdew.lib.multiblock.network.{MsgOutputCfgPayload, MsgOutputCfgRSMode}
  def handleConfigPacket(m: MsgOutputCfgPayload): Unit = m match {
    case MsgOutputCfgRSMode(r) => rsMode = r
import net.bdew.lib.multiblock.network.{MsgOutputCfgPayload, MsgOutputCfgRSMode}
  def handleConfigPacket(m: MsgOutputCfgPayload): Unit = m match {
    case MsgOutputCfgRSMode(r) => rsMode = r
import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgRSMode, MultiblockNetHandler}
    MultiblockNetHandler.sendToServer(MsgOutputCfg(output, MsgOutputCfgRSMode(next(cfg.rsMode))))
trait MsgOutputCfgPayload extends Serializable

case class MsgOutputCfg(output: Int, payload: MsgOutputCfgPayload) extends MultiblockNetHandler.Message
case class MsgOutputCfgRSMode(rsMode: RSMode.Value) extends MsgOutputCfgPayload
case class MsgOutputCfgSlot(slot: String) extends MsgOutputCfgPayload
object MultiblockNetHandler extends NetChannel("multiblock", "1") {
  regServerContainerHandler(1, classOf[MsgOutputCfg], classOf[ContainerOutputFaces]) { (msg, cont, _) =>
    cont.te.outputConfig(msg.output).handleConfigPacket(msg.payload)
abstract class BaseMessage[CH <: NetChannel] extends Serializable
package net.bdew.lib.network

import net.minecraft.nbt.CompoundNBT

import java.io.{ObjectInputStream, ObjectOutputStream}

class CompoundNBTSerialize(var tag: CompoundNBT = new CompoundNBT) extends Serializable {
  private def writeObject(out: ObjectOutputStream): Unit = {
    out.writeObject(NBTHelper.toBytes(tag))
  }

  private def readObject(in: ObjectInputStream): Unit = {
    tag = NBTHelper.fromBytes(in.readObject().asInstanceOf[Array[Byte]])
  }
}

object CompoundNBTSerialize {

  import scala.language.implicitConversions

  implicit def ser2content(v: CompoundNBTSerialize): CompoundNBT = v.tag
  implicit def content2ser(v: CompoundNBT): CompoundNBTSerialize = new CompoundNBTSerialize(v)
}
import io.netty.buffer.{ByteBufInputStream, ByteBufOutputStream}
import java.io.{ObjectInputStream, ObjectOutputStream}
  private def encodeMsg[M <: Message](m: Message, p: PacketBuffer): Unit = {
    val writer = new ObjectOutputStream(new ByteBufOutputStream(p))
    writer.writeObject(m)
  }

  private def decodeMsg[M <: Message](p: PacketBuffer): M = {
    val reader = new ObjectInputStream(new ByteBufInputStream(p))
    reader.readObject().asInstanceOf[M]
  def regServerHandler[M <: Message](id: Int, cls: Class[M])(handler: (M, NetworkEvent.Context) => Unit): Unit = {
    channel.registerMessage[M](id, cls, (m, p) => encodeMsg[M](m, p), m => decodeMsg(m),
  def regServerContainerHandler[M <: Message, C <: Container](id: Int, cls: Class[M], cont: Class[C])(handler: (M, C, NetworkEvent.Context) => Unit): Unit = {
    regServerHandler(id, cls) { (msg, ctx) =>
  def regClientHandler[M <: Message](id: Int, cls: Class[M])(handler: M => Unit): Unit = {
    channel.registerMessage[M](id, cls, (m, p) => encodeMsg[M](m, p), m => decodeMsg(m),
Fixed code: mod.version=1.16.0
import net.bdew.lib.multiblock.network.MsgOutputCfg
  def handleConfigPacket(m: MsgOutputCfg): Unit
  def handleConfigPacket(m: MsgOutputCfg): Unit = throw new NotImplementedException
import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgRSMode}
  def handleConfigPacket(m: MsgOutputCfg): Unit = m match {
    case MsgOutputCfgRSMode(_, r) => rsMode = r
import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgSlot}
  override def handleConfigPacket(m: MsgOutputCfg): Unit = m match {
    case MsgOutputCfgSlot(_, id) => slot = slotsDef.get(id)
import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgRSMode}
  def handleConfigPacket(m: MsgOutputCfg): Unit = m match {
    case MsgOutputCfgRSMode(_, r) => rsMode = r
import net.bdew.lib.multiblock.network.{MsgOutputCfg, MsgOutputCfgRSMode}
  def handleConfigPacket(m: MsgOutputCfg): Unit = m match {
    case MsgOutputCfgRSMode(_, r) => rsMode = r
import net.bdew.lib.multiblock.network.{MsgOutputCfgRSMode, MultiblockNetHandler}
    MultiblockNetHandler.sendToServer(MsgOutputCfgRSMode(output, next(cfg.rsMode)))
trait MsgOutputCfg {
  def output: Int
}
import net.minecraft.network.PacketBuffer
case class MsgOutputCfgRSMode(output: Int, rsMode: RSMode.Value) extends MultiblockNetHandler.Message with MsgOutputCfg

object CodecOutputCfgRSMode extends MultiblockNetHandler.Codec[MsgOutputCfgRSMode] {
  override def encodeMsg(m: MsgOutputCfgRSMode, p: PacketBuffer): Unit = {
    p.writeVarInt(m.output)
    p.writeByte(m.rsMode.id)
  }

  override def decodeMsg(p: PacketBuffer): MsgOutputCfgRSMode =
    MsgOutputCfgRSMode(p.readVarInt(), RSMode(p.readByte()))
}
import net.minecraft.network.PacketBuffer

case class MsgOutputCfgSlot(output: Int, slot: String) extends MultiblockNetHandler.Message with MsgOutputCfg

object CodecOutputCfgSlot extends MultiblockNetHandler.Codec[MsgOutputCfgSlot] {
  override def encodeMsg(m: MsgOutputCfgSlot, p: PacketBuffer): Unit = {
    p.writeVarInt(m.output)
    p.writeUtf(m.slot)
  }

  override def decodeMsg(p: PacketBuffer): MsgOutputCfgSlot = {
    MsgOutputCfgSlot(p.readVarInt(), p.readUtf(100))
  }
}
object MultiblockNetHandler extends NetChannel("multiblock", "2") {
  private def forwardConfigMsg(m: MsgOutputCfg, cont: ContainerOutputFaces): Unit = {
    cont.te.outputConfig(m.output).handleConfigPacket(m)

  regServerContainerHandler(1, CodecOutputCfgRSMode, classOf[ContainerOutputFaces]) { (msg, cont, _) =>
    forwardConfigMsg(msg, cont)
  }

  regServerContainerHandler(2, CodecOutputCfgSlot, classOf[ContainerOutputFaces]) { (msg, cont, _) =>
    forwardConfigMsg(msg, cont)
  }
abstract class BaseMessage[CH <: NetChannel]
import scala.reflect.ClassTag
  abstract class Codec[M <: Message : ClassTag] {
    val cls: Class[M] = implicitly[ClassTag[M]].runtimeClass.asInstanceOf[Class[M]]
    def encodeMsg(m: M, p: PacketBuffer): Unit
    def decodeMsg(p: PacketBuffer): M
  def regServerHandler[M <: Message](id: Int, codec: Codec[M])(handler: (M, NetworkEvent.Context) => Unit): Unit = {
    channel.registerMessage[M](id, codec.cls, codec.encodeMsg, codec.decodeMsg,
  def regServerContainerHandler[M <: Message, C <: Container](id: Int, codec: Codec[M], cont: Class[C])(handler: (M, C, NetworkEvent.Context) => Unit): Unit = {
    regServerHandler(id, codec) { (msg, ctx) =>
  def regClientHandler[M <: Message](id: Int, codec: Codec[M])(handler: M => Unit): Unit = {
    channel.registerMessage[M](id, codec.cls, codec.encodeMsg, codec.decodeMsg,