diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 1eb8898d..4f7bc0aa 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -481,7 +481,7 @@ object GamePacketOpcode extends Enumeration {
case 0x87 => noDecoder(ObjectDeployedCountMessage)
// 0x88
case 0x88 => game.WeaponDelayFireMessage.decode
- case 0x89 => noDecoder(BugReportMessage)
+ case 0x89 => game.BugReportMessage.decode
case 0x8a => noDecoder(PlayerStasisMessage)
case 0x8b => noDecoder(UnknownMessage139)
case 0x8c => noDecoder(OutfitMembershipRequest)
diff --git a/common/src/main/scala/net/psforever/packet/game/BugReportMessage.scala b/common/src/main/scala/net/psforever/packet/game/BugReportMessage.scala
new file mode 100644
index 00000000..f444591e
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/BugReportMessage.scala
@@ -0,0 +1,74 @@
+// Copyright (c) 2016 PSForever.net to present
+package net.psforever.packet.game
+
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.types.Vector3
+import scodec.Codec
+import scodec.codecs._
+
+/**
+ * An `Enumeration` of the kinds of bugs applicable to the reporting system.
+ */
+object BugType extends Enumeration {
+ type Type = Value
+ val CRASH,
+ GAMEPLAY,
+ ART,
+ SOUND,
+ HARDWARE,
+ OTHER
+ = Value
+
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
+}
+
+/**
+ * Allow the user to report a bug they have found in the game.
+ *
+ * Bug reports are prepended by the version of the client on which the player is encountering the issue.
+ * The last delivered client by Sony Online Entertainment was `3.15.84` with date `Dec 2 2009`.
+ *
+ * The path of bug reports submitted to the game's official server is not known.
+ * @param version_major the client's major version number
+ * @param version_minor the client's minor version number
+ * @param version_date the date the client was compiled
+ * @param bug_type the kind of bug that took place
+ * @param repeatable whether the bug is repeatable
+ * @param unk na;
+ * always 0?
+ * @param zone which zone the bug took place
+ * @param pos the location where ther bug took place
+ * @param summary a short explanation of the bug
+ * @param desc a detailed explanation of the bug
+ */
+final case class BugReportMessage(version_major : Long,
+ version_minor : Long,
+ version_date : String,
+ bug_type : BugType.Value,
+ repeatable : Boolean,
+ unk : Int,
+ zone : Int,
+ pos : Vector3,
+ summary : String,
+ desc : String)
+ extends PlanetSideGamePacket {
+ type Packet = BugReportMessage
+ def opcode = GamePacketOpcode.BugReportMessage
+ def encode = BugReportMessage.encode(this)
+}
+
+object BugReportMessage extends Marshallable[BugReportMessage] {
+ implicit val codec : Codec[BugReportMessage] = (
+ ("versionMajor" | uint32L) ::
+ ("versionMinor" | uint32L) ::
+ ("versionDate" | PacketHelpers.encodedString) ::
+ ("bug_type" | BugType.codec) ::
+ ignore(3) ::
+ ("repeatable" | bool) ::
+ ("unk" | uint4L) ::
+ ("zone" | uint8L) ::
+ ("pos" | Vector3.codec_pos) ::
+ ("summary" | PacketHelpers.encodedWideStringAligned(4)) ::
+ ("desc" | PacketHelpers.encodedWideString)
+ ).as[BugReportMessage]
+}
diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala
index 6faa6e6e..da1ae742 100644
--- a/common/src/test/scala/GamePacketTest.scala
+++ b/common/src/test/scala/GamePacketTest.scala
@@ -1325,6 +1325,38 @@ class GamePacketTest extends Specification {
}
}
+ "BugReportMessage" should {
+ val string = hex"89 03000000 0F000000 8B4465632020322032303039 1 1 0 19 6C511 656B1 7A11 830610062006300 843100320033003400"
+
+ "decode" in {
+ PacketCoding.DecodePacket(string).require match {
+ case BugReportMessage(major, minor, date, btype, repeat, unk, zone, loc, summary, desc) =>
+ major mustEqual 3
+ minor mustEqual 15
+ date mustEqual "Dec 2 2009"
+ btype mustEqual BugType.GAMEPLAY
+ repeat mustEqual true
+ zone mustEqual 25
+ loc.x mustEqual 674.84375f
+ loc.y mustEqual 726.78906f
+ loc.z mustEqual 69.90625f
+ summary mustEqual "abc"
+ desc mustEqual "1234"
+ case default =>
+ ko
+ }
+ }
+
+ "encode" in {
+ val msg = BugReportMessage(3, 15, "Dec 2 2009",
+ BugType.GAMEPLAY, true, 0, 25, Vector3(674.84375f, 726.78906f, 69.90625f),
+ "abc", "1234")
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string
+ }
+ }
+
"ContinentalLockUpdateMessage" should {
val string = hex"A8 16 00 40"