diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index a502732a5..757772ee2 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -571,7 +571,7 @@ object GamePacketOpcode extends Enumeration { case 0xd2 => noDecoder(RespawnAMSInfoMessage) case 0xd3 => noDecoder(ComponentDamageMessage) case 0xd4 => noDecoder(GenericObjectActionAtPositionMessage) - case 0xd5 => noDecoder(PropertyOverrideMessage) + case 0xd5 => game.PropertyOverrideMessage.decode case 0xd6 => noDecoder(WarpgateLinkOverrideMessage) case 0xd7 => noDecoder(EmpireBenefitsMessage) // 0xd8 diff --git a/common/src/main/scala/net/psforever/packet/game/PropertyOverrideMessage.scala b/common/src/main/scala/net/psforever/packet/game/PropertyOverrideMessage.scala new file mode 100644 index 000000000..1aab5e3c2 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/PropertyOverrideMessage.scala @@ -0,0 +1,115 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ +import shapeless.{::, HNil} + +final case class PropertyOverrideMessage(list : List[PropertyOverrideMessage.GameProperty]) + extends PlanetSideGamePacket { + type Packet = PropertyOverrideMessage + def opcode = GamePacketOpcode.PropertyOverrideMessage + def encode = PropertyOverrideMessage.encode(this) +} + +object GamePropertyValues { + def apply(field1 : String) : PropertyOverrideMessage.GamePropertyValues = { + PropertyOverrideMessage.GamePropertyValues(field1, "") + } + + def apply(field1 : String, field2 : String) : PropertyOverrideMessage.GamePropertyValues = { + PropertyOverrideMessage.GamePropertyValues(field1, field2) + } +} + +object GamePropertyField { + def apply(unk : Int) : PropertyOverrideMessage.GamePropertyField = { + PropertyOverrideMessage.GamePropertyField(unk, Nil) + } + + def apply(unk : Int, list : List[PropertyOverrideMessage.GamePropertyValues]) : PropertyOverrideMessage.GamePropertyField = { + PropertyOverrideMessage.GamePropertyField(unk, list) + } +} + +object GameProperty { + def apply(unk : Int) : PropertyOverrideMessage.GameProperty = { + PropertyOverrideMessage.GameProperty(unk, Nil) + } + + def apply(unk : Int, list : List[PropertyOverrideMessage.GamePropertyField]) : PropertyOverrideMessage.GameProperty = { + PropertyOverrideMessage.GameProperty(unk, list) + } +} + +object PropertyOverrideMessage extends Marshallable[PropertyOverrideMessage] { + final case class GamePropertyValues(field1 : String, field2 : String) + + final case class GamePropertyField(unk : Int, list : List[GamePropertyValues]) + + final case class GameProperty(unk : Int, list : List[GamePropertyField]) + + private def value_pair_aligned_codec(n : Int) : Codec[GamePropertyValues] = ( + ("field1" | PacketHelpers.encodedStringAligned(n)) :: + ("field2" | PacketHelpers.encodedString) + ).as[GamePropertyValues] + + private val value_pair_codec : Codec[GamePropertyValues] = ( + ("field1" | PacketHelpers.encodedString) :: + ("field2" | PacketHelpers.encodedString) + ).as[GamePropertyValues] + + private def game_subproperty_codec(n : Int) : Codec[GamePropertyField] = ( + ("unk" | uintL(11)) :: + (("len" | uint16L) >>:~ { len => + conditional(len > 0, value_pair_aligned_codec(n)) :: + conditional(len > 1, PacketHelpers.listOfNSized((len - 1).toLong, value_pair_codec)) + }) + ).xmap[GamePropertyField] ( + { + case unk :: _ :: Some(first) :: None :: HNil => + GamePropertyField(unk, first :: Nil) + + case unk :: _ :: Some(first) :: Some(other) :: HNil => + GamePropertyField(unk, first +: other) + }, + { + case GamePropertyField(unk, list) => + val (first, other) = list match { + case ((f : GamePropertyValues) +: (rest : List[GamePropertyValues])) => (Some(f), Some(rest)) + case (f : GamePropertyValues) +: Nil => (Some(f), None) + case Nil => (None, None) + } + unk :: list.length :: first :: other :: HNil + } + ) + + private val game_property_codec : Codec[GameProperty] = ( + ("unk" | uint16L) :: + (("len" | uintL(11)) >>:~ { len => + conditional(len > 0, game_subproperty_codec(2)) :: + conditional(len > 1, PacketHelpers.listOfNSized((len - 1).toLong, game_subproperty_codec(5))) + }) + ).xmap[GameProperty] ( + { + case unk :: _ :: Some(first) :: None :: HNil => + GameProperty(unk, first :: Nil) + + case unk :: _ :: Some(first) :: Some(other) :: HNil => + GameProperty(unk, first +: other) + }, + { + case GameProperty(unk, list) => + val (first, other) = list match { + case ((f : GamePropertyField) +: (rest : List[GamePropertyField])) => (Some(f), Some(rest)) + case (f : GamePropertyField) +: Nil => (Some(f), None) + case Nil => (None, None) + } + unk :: list.length :: first :: other :: HNil + } + ) + + implicit val codec : Codec[PropertyOverrideMessage] = + listOfN(uint16L, game_property_codec).as[PropertyOverrideMessage] +} diff --git a/common/src/test/scala/game/PropertyOverrideMessageTest.scala b/common/src/test/scala/game/PropertyOverrideMessageTest.scala new file mode 100644 index 000000000..09c40d80f --- /dev/null +++ b/common/src/test/scala/game/PropertyOverrideMessageTest.scala @@ -0,0 +1,28 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class PropertyOverrideMessageTest extends Specification { + val string = hex"D5 0B 00 00 00 01 0A E4 0C 02 48 70 75 72 63 68 61 73 65 5F 65 78 65 6D 70 74 5F 76 73 80 92 70 75 72 63 68 61 73 65 5F 65 78 65 6D 70 74 5F 74 72 80 92 70 75 72 63 68 61 73 65 5F 65 78 65 6D 70 74 5F 6E 63 80 11 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 12 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 13 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 14 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 15 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 16 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 1D 00 15 0A 60 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 54 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 76 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 87 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C7 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C8 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 26 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 52 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 AD 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B0 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CE 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 D6 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 2C 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 82 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 83 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CA 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 61 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 9B 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 DA 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 1E 00 15 0A 60 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 54 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 76 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 87 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C7 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C8 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 26 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 52 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 AD 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B0 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CE 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 D6 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 2C 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 82 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 83 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CA 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 61 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 9B 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 DA 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 1F 00 15 0A 60 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 54 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 76 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 87 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C7 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C8 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 26 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 52 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 AD 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B0 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CE 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 D6 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 2C 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 82 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 83 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CA 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 61 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 9B 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 DA 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 20 00 15 0A 60 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 54 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 76 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 87 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C7 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C8 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 26 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 52 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 AD 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B0 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CE 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 D6 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 2C 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 82 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 83 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CA 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 61 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 9B 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 DA 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case PropertyOverrideMessage(list) => + //List(GameProperty(0,List(GamePropertyField(343,List(GamePropertyValues(purchase_exempt_vs,), GamePropertyValues(purchase_exempt_tr,), GamePropertyValues(purchase_exempt_nc,))))), GameProperty(17,List(GamePropertyField(421,List(GamePropertyValues(allowed,false))))), GameProperty(18,List(GamePropertyField(421,List(GamePropertyValues(allowed,false))))), GameProperty(19,List(GamePropertyField(421,List(GamePropertyValues(allowed,false))))), GameProperty(20,List(GamePropertyField(421,List(GamePropertyValues(allowed,false))))), GameProperty(21,List(GamePropertyField(421,List(GamePropertyValues(allowed,false))))), GameProperty(22,List(GamePropertyField(421,List(GamePropertyValues(allowed,false))))), GameProperty(29,List(GamePropertyField(83,List(GamePropertyValues(allowed,false))), GamePropertyField(84,List(GamePropertyValues(allowed,false))), GamePropertyField(118,List(GamePropertyValues(allowed,false))), GamePropertyField(135,List(GamePropertyValues(allowed,false))), GamePropertyField(199,List(GamePropertyValues(allowed,false))), GamePropertyField(200,List(GamePropertyValues(allowed,false))), GamePropertyField(294,List(GamePropertyValues(allowed,false))), GamePropertyField(338,List(GamePropertyValues(allowed,false))), GamePropertyField(429,List(GamePropertyValues(allowed,false))), GamePropertyField(432,List(GamePropertyValues(allowed,false))), GamePropertyField(441,List(GamePropertyValues(allowed,false))), GamePropertyField(462,List(GamePropertyValues(allowed,false))), GamePropertyField(470,List(GamePropertyValues(allowed,false))), GamePropertyField(556,List(GamePropertyValues(allowed,false))), GamePropertyField(642,List(GamePropertyValues(allowed,false))), GamePropertyField(643,List(GamePropertyValues(allowed,false))), GamePropertyField(697,List(GamePropertyValues(allowed,false))), GamePropertyField(714,List(GamePropertyValues(allowed,false))), GamePropertyField(865,List(GamePropertyValues(allowed,false))), GamePropertyField(923,List(GamePropertyValues(allowed,false))), GamePropertyField(986,List(GamePropertyValues(allowed,false))))), GameProperty(30,List(GamePropertyField(83,List(GamePropertyValues(allowed,false))), GamePropertyField(84,List(GamePropertyValues(allowed,false))), GamePropertyField(118,List(GamePropertyValues(allowed,false))), GamePropertyField(135,List(GamePropertyValues(allowed,false))), GamePropertyField(199,List(GamePropertyValues(allowed,false))), GamePropertyField(200,List(GamePropertyValues(allowed,false))), GamePropertyField(294,List(GamePropertyValues(allowed,false))), GamePropertyField(338,List(GamePropertyValues(allowed,false))), GamePropertyField(429,List(GamePropertyValues(allowed,false))), GamePropertyField(432,List(GamePropertyValues(allowed,false))), GamePropertyField(441,List(GamePropertyValues(allowed,false))), GamePropertyField(462,List(GamePropertyValues(allowed,false))), GamePropertyField(470,List(GamePropertyValues(allowed,false))), GamePropertyField(556,List(GamePropertyValues(allowed,false))), GamePropertyField(642,List(GamePropertyValues(allowed,false))), GamePropertyField(643,List(GamePropertyValues(allowed,false))), GamePropertyField(697,List(GamePropertyValues(allowed,false))), GamePropertyField(714,List(GamePropertyValues(allowed,false))), GamePropertyField(865,List(GamePropertyValues(allowed,false))), GamePropertyField(923,List(GamePropertyValues(allowed,false))), GamePropertyField(986,List(GamePropertyValues(allowed,false))))), GameProperty(31,List(GamePropertyField(83,List(GamePropertyValues(allowed,false))), GamePropertyField(84,List(GamePropertyValues(allowed,false))), GamePropertyField(118,List(GamePropertyValues(allowed,false))), GamePropertyField(135,List(GamePropertyValues(allowed,false))), GamePropertyField(199,List(GamePropertyValues(allowed,false))), GamePropertyField(200,List(GamePropertyValues(allowed,false))), GamePropertyField(294,List(GamePropertyValues(allowed,false))), GamePropertyField(338,List(GamePropertyValues(allowed,false))), GamePropertyField(429,List(GamePropertyValues(allowed,false))), GamePropertyField(432,List(GamePropertyValues(allowed,false))), GamePropertyField(441,List(GamePropertyValues(allowed,false))), GamePropertyField(462,List(GamePropertyValues(allowed,false))), GamePropertyField(470,List(GamePropertyValues(allowed,false))), GamePropertyField(556,List(GamePropertyValues(allowed,false))), GamePropertyField(642,List(GamePropertyValues(allowed,false))), GamePropertyField(643,List(GamePropertyValues(allowed,false))), GamePropertyField(697,List(GamePropertyValues(allowed,false))), GamePropertyField(714,List(GamePropertyValues(allowed,false))), GamePropertyField(865,List(GamePropertyValues(allowed,false))), GamePropertyField(923,List(GamePropertyValues(allowed,false))), GamePropertyField(986,List(GamePropertyValues(allowed,false))))), GameProperty(32,List(GamePropertyField(83,List(GamePropertyValues(allowed,false))), GamePropertyField(84,List(GamePropertyValues(allowed,false))), GamePropertyField(118,List(GamePropertyValues(allowed,false))), GamePropertyField(135,List(GamePropertyValues(allowed,false))), GamePropertyField(199,List(GamePropertyValues(allowed,false))), GamePropertyField(200,List(GamePropertyValues(allowed,false))), GamePropertyField(294,List(GamePropertyValues(allowed,false))), GamePropertyField(338,List(GamePropertyValues(allowed,false))), GamePropertyField(429,List(GamePropertyValues(allowed,false))), GamePropertyField(432,List(GamePropertyValues(allowed,false))), GamePropertyField(441,List(GamePropertyValues(allowed,false))), GamePropertyField(462,List(GamePropertyValues(allowed,false))), GamePropertyField(470,List(GamePropertyValues(allowed,false))), GamePropertyField(556,List(GamePropertyValues(allowed,false))), GamePropertyField(642,List(GamePropertyValues(allowed,false))), GamePropertyField(643,List(GamePropertyValues(allowed,false))), GamePropertyField(697,List(GamePropertyValues(allowed,false))), GamePropertyField(714,List(GamePropertyValues(allowed,false))), GamePropertyField(865,List(GamePropertyValues(allowed,false))), GamePropertyField(923,List(GamePropertyValues(allowed,false))), GamePropertyField(986,List(GamePropertyValues(allowed,false)))))) + ok + case _ => + ko + } + } + + "encode" in { + ok +// val msg = ProximityTerminalUseMessage(PlanetSideGUID(75), PlanetSideGUID(167), true) +// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector +// pkt mustEqual string + } +}