mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-14 16:04:41 +00:00
added params for all DetailedCharacterData regions; wrote a function to handle proper string padding for the latter half of DCD (DCB); repaired all existing DetailedCharacterData tests
This commit is contained in:
parent
59569f1a7d
commit
3ac0010052
3 changed files with 1361 additions and 610 deletions
|
|
@ -288,7 +288,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
("unk1" | bool) :: //serves a different internal purpose depending on the state of alt_model
|
("unk1" | bool) :: //serves a different internal purpose depending on the state of alt_model
|
||||||
(conditional(false, "unk2" | extra_codec) >>:~ { extra => //TODO not sure what causes this branch
|
(conditional(false, "unk2" | extra_codec) >>:~ { extra => //TODO not sure what causes this branch
|
||||||
("jammered" | bool) ::
|
("jammered" | bool) ::
|
||||||
optional(bool, "unk3" | uint16L) :: //TODO factor 16u into bitsize
|
optional(bool, "unk3" | uint16L) ::
|
||||||
("unk4" | uint16L) ::
|
("unk4" | uint16L) ::
|
||||||
("name" | PacketHelpers.encodedWideStringAligned(namePadding(name_padding, extra))) ::
|
("name" | PacketHelpers.encodedWideStringAligned(namePadding(name_padding, extra))) ::
|
||||||
("exosuit" | ExoSuitType.codec) ::
|
("exosuit" | ExoSuitType.codec) ::
|
||||||
|
|
@ -363,22 +363,12 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case CharacterAppearanceB(u0, outfit, logo, u1, bpack, u2, u3, u4, facingPitch, facingYawUpper, lfs, gstate, cloaking, u5, u6, charging, u7, zipline) =>
|
case CharacterAppearanceB(u0, outfit, logo, u1, bpack, u2, u3, u4, facingPitch, facingYawUpper, lfs, gstate, cloaking, u5, u6, charging, u7, zipline) =>
|
||||||
val u0Long = if(u0 == 0) {
|
val u0Long = if(u0 == 0 && outfit.nonEmpty) {
|
||||||
if(outfit.length == 0) {
|
outfit.length.toLong
|
||||||
u0
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
outfit.length.toLong
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(outfit.length == 0) {
|
u0
|
||||||
0L
|
} //TODO this is a kludge; unk0 must be (some) non-zero if outfit_name is defined
|
||||||
}
|
|
||||||
else {
|
|
||||||
u0
|
|
||||||
}
|
|
||||||
} //TODO this is a kludge; unk0 must be non-zero if outfit_name is defined, and zero if empty
|
|
||||||
val (bpackOpt, zipOpt) = if(alt_model) {
|
val (bpackOpt, zipOpt) = if(alt_model) {
|
||||||
val bpackOpt = if(bpack) { Some(true) } else { None }
|
val bpackOpt = if(bpack) { Some(true) } else { None }
|
||||||
(bpackOpt, zipline)
|
(bpackOpt, zipline)
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,24 @@ import scala.annotation.tailrec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An entry in the `List` of valid implant slots in `DetailedCharacterData`.
|
* An entry in the `List` of valid implant slots in `DetailedCharacterData`.
|
||||||
* `activation`, if defined, indicates the time remaining (in seconds?) before an implant becomes usable.
|
|
||||||
* @param implant the type of implant
|
* @param implant the type of implant
|
||||||
* @param activation the activation timer;
|
* technically, this is unconfirmed
|
||||||
* technically, this is "unconfirmed"
|
* @param active whether this implant is turned on;
|
||||||
|
* technically, this is unconfirmed
|
||||||
* @see `ImplantType`
|
* @see `ImplantType`
|
||||||
*/
|
*/
|
||||||
final case class ImplantEntry(implant : ImplantType.Value,
|
final case class ImplantEntry(implant : ImplantType.Value,
|
||||||
activation : Option[Int]) extends StreamBitSize {
|
initialization : Option[Int],
|
||||||
|
active : Boolean) extends StreamBitSize {
|
||||||
override def bitsize : Long = {
|
override def bitsize : Long = {
|
||||||
val activationSize = if(activation.isDefined) { 8L } else { 1L }
|
val timerSize = initialization match { case Some(_) => 8L ; case None => 1L }
|
||||||
9L + activationSize
|
9L + timerSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ImplantEntry {
|
||||||
|
def apply(implant : ImplantType.Value, initialization : Option[Int]) : ImplantEntry = {
|
||||||
|
ImplantEntry(implant, initialization, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,14 +44,9 @@ final case class DCDExtra2(unk1 : Int,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
|
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
|
||||||
* <br>
|
* @see `CharacterData`
|
||||||
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.
|
* @see `CertificationType`
|
||||||
* It goes into depth about information related to the given character in-game career that is not revealed to other players.
|
|
||||||
* To be specific, it passes more thorough data about the character that the client can display to the owner of the client.
|
|
||||||
* For example, health is a full number, rather than a percentage, as is the case with `CharacterData`.
|
|
||||||
* Just as prominent is the list of first time events and the list of completed tutorials.
|
|
||||||
* Additionally, a full inventory, as opposed to the initial five weapon slots.
|
|
||||||
* @param bep the avatar's battle experience points, which determines his Battle Rank
|
* @param bep the avatar's battle experience points, which determines his Battle Rank
|
||||||
* @param cep the avatar's command experience points, which determines his Command Rank
|
* @param cep the avatar's command experience points, which determines his Command Rank
|
||||||
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
|
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
|
||||||
|
|
@ -58,7 +60,35 @@ final case class DCDExtra2(unk1 : Int,
|
||||||
* range is 0-65535
|
* range is 0-65535
|
||||||
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value;
|
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value;
|
||||||
* range is 0-65535
|
* range is 0-65535
|
||||||
* @param certs the `List` of active certifications
|
* @param certs the `List` of certifications
|
||||||
|
*/
|
||||||
|
final case class DetailedCharacterA(bep : Long,
|
||||||
|
cep : Long,
|
||||||
|
unk1 : Long,
|
||||||
|
unk2 : Long,
|
||||||
|
unk3 : Long,
|
||||||
|
healthMax : Int,
|
||||||
|
health : Int,
|
||||||
|
unk4 : Boolean,
|
||||||
|
armor : Int,
|
||||||
|
unk5 : Long,
|
||||||
|
staminaMax : Int,
|
||||||
|
stamina : Int,
|
||||||
|
unk6 : Int,
|
||||||
|
unk7 : Int,
|
||||||
|
unk8 : Long,
|
||||||
|
unk9 : List[Int],
|
||||||
|
certs : List[CertificationType.Value]) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = {
|
||||||
|
val certSize : Long = certs.length * 8
|
||||||
|
428L + certSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
|
||||||
|
* @see `CharacterData`
|
||||||
|
* @see `Cosmetics`
|
||||||
* @param implants the `List` of implant slots currently possessed by this avatar
|
* @param implants the `List` of implant slots currently possessed by this avatar
|
||||||
* @param firstTimeEvents the list of first time events performed by this avatar;
|
* @param firstTimeEvents the list of first time events performed by this avatar;
|
||||||
* the size field is a 32-bit number;
|
* the size field is a 32-bit number;
|
||||||
|
|
@ -69,73 +99,87 @@ final case class DCDExtra2(unk1 : Int,
|
||||||
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
|
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
|
||||||
* they become available at battle rank 24;
|
* they become available at battle rank 24;
|
||||||
* these flags do not exist if they are not applicable
|
* these flags do not exist if they are not applicable
|
||||||
* @see `CharacterData`<br>
|
|
||||||
* `CertificationType`
|
|
||||||
*/
|
*/
|
||||||
final case class DetailedCharacterData(bep : Long,
|
final case class DetailedCharacterB(unk1 : Option[Long],
|
||||||
cep : Long,
|
implants : List[ImplantEntry],
|
||||||
healthMax : Int,
|
unk2 : List[DCDExtra1],
|
||||||
health : Int,
|
unk3 : List[DCDExtra1],
|
||||||
armor : Int,
|
firstTimeEvents : List[String],
|
||||||
staminaMax : Int,
|
tutorials : List[String],
|
||||||
stamina : Int,
|
unk4 : Long,
|
||||||
certs : List[CertificationType.Value],
|
unk5 : Long,
|
||||||
unk1 : Option[Long],
|
unk6 : Long,
|
||||||
implants : List[ImplantEntry],
|
unk7 : Long,
|
||||||
unk2 : List[DCDExtra1],
|
unk8 : Long,
|
||||||
unk3 : List[DCDExtra1],
|
unk9 : Option[DCDExtra2],
|
||||||
firstTimeEvents : List[String],
|
unkA : List[Long],
|
||||||
tutorials : List[String],
|
unkB : List[String],
|
||||||
cosmetics : Option[Cosmetics])
|
unkC : Boolean,
|
||||||
(pad_length : Option[Int]) extends ConstructorData {
|
cosmetics : Option[Cosmetics])
|
||||||
|
(bep : Long,
|
||||||
|
pad_length : Option[Int]) extends StreamBitSize {
|
||||||
override def bitsize : Long = {
|
override def bitsize : Long = {
|
||||||
//factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated
|
|
||||||
//cert list
|
|
||||||
val certSize = (certs.length + 1) * 8
|
|
||||||
//unk1
|
//unk1
|
||||||
val unk1Size = if(unk1.isDefined) { 32L } else { 0L }
|
val unk1Size = unk1 match { case Some(_) => 32L ; case None => 0L }
|
||||||
//implant list
|
//implant list
|
||||||
var implantSize : Long = 0L
|
val implantSize : Long = implants.foldLeft(0L)(_ + _.bitsize)
|
||||||
for(entry <- implants) {
|
|
||||||
implantSize += entry.bitsize
|
|
||||||
}
|
|
||||||
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length)
|
|
||||||
//fte list
|
//fte list
|
||||||
val fteLen = firstTimeEvents.size
|
val fteLen = firstTimeEvents.size
|
||||||
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding)
|
val eventListSize : Long = firstTimeEvents.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
|
||||||
for(str <- firstTimeEvents) {
|
//tutorial list
|
||||||
eventListSize += StreamBitSize.stringBitSize(str)
|
val tutLen = tutorials.size
|
||||||
}
|
val tutorialListSize : Long = tutorials.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
|
||||||
//unk2, unk3, TODO padding
|
|
||||||
val unk2Len = unk2.size
|
val unk2Len = unk2.size
|
||||||
val unk3Len = unk3.size
|
val unk3Len = unk3.size
|
||||||
val unkAllLen = unk2Len + unk3Len
|
val unkAllLen = unk2Len + unk3Len
|
||||||
val unk2_3ListSize : Long = 16L + (if(unk2Len > 0) {
|
val unk2_3ListSize : Long = if(unk2Len > 0) {
|
||||||
unkAllLen * unk2.head.bitsize
|
unkAllLen * unk2.head.bitsize
|
||||||
}
|
}
|
||||||
else if(unk3Len > 0) {
|
else if(unk3Len > 0) {
|
||||||
unkAllLen * unk3.head.bitsize
|
unkAllLen * unk3.head.bitsize
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
0
|
0L
|
||||||
})
|
|
||||||
//tutorial list
|
|
||||||
val tutLen = tutorials.size
|
|
||||||
var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, implantPadding)
|
|
||||||
for(str <- tutorials) {
|
|
||||||
tutorialListSize += StreamBitSize.stringBitSize(str)
|
|
||||||
}
|
}
|
||||||
//character is at least BR24
|
//character is at least BR24
|
||||||
val br24 = DetailedCharacterData.isBR24(bep)
|
val br24 = DetailedCharacterData.isBR24(bep)
|
||||||
val extraBitSize : Long = if(br24) { 0L } else { 13L }
|
val unk9Size : Long = if(br24) { 0L } else { 13L }
|
||||||
//TODO DCDExtra2
|
val unkASize : Long = unkA.length * 32L
|
||||||
//TODO last List of String values, and padding
|
val unkBSize : Long = unkB.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
|
||||||
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
|
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
|
||||||
615L + certSize + unk1Size + implantSize + eventListSize + unk2_3ListSize + tutorialListSize + extraBitSize + cosmeticsSize
|
|
||||||
|
val paddingSize : Int =
|
||||||
|
DetailedCharacterData.paddingCalculations(pad_length, implants, Nil)(unk2Len) + /* unk2 */
|
||||||
|
DetailedCharacterData.paddingCalculations(pad_length, implants, List(unk2))(unk3Len) + /* unk3 */
|
||||||
|
DetailedCharacterData.paddingCalculations(pad_length, implants, List(unk3, unk2))(fteLen) + /* firstTimeEvents */
|
||||||
|
DetailedCharacterData.paddingCalculations(pad_length, implants, List(firstTimeEvents, unk3, unk2))(tutLen) + /* tutorials */
|
||||||
|
DetailedCharacterData.paddingCalculations(
|
||||||
|
DetailedCharacterData.displaceByUnk9(pad_length, unk9, 5),
|
||||||
|
implants,
|
||||||
|
List(DetailedCharacterData.optToList(unk9), tutorials, firstTimeEvents, unk3, unk2)
|
||||||
|
)(unkB.length) /* unkB */
|
||||||
|
275L + unk1Size + implantSize + eventListSize + unk2_3ListSize + tutorialListSize + unk9Size + unkASize + unkBSize + cosmeticsSize + paddingSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
|
||||||
|
* <br>
|
||||||
|
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.
|
||||||
|
* It goes into depth about information related to the given character in-game career that is not revealed to other players.
|
||||||
|
* To be specific, it passes more thorough data about the character that the client can display to the owner of the client.
|
||||||
|
* For example, health is a full number, rather than a percentage, as is the case with `CharacterData`.
|
||||||
|
* Just as prominent is the list of first time events and the list of completed tutorials.
|
||||||
|
* Additionally, a full inventory, as opposed to the initial five weapon slots.
|
||||||
|
* @see `CharacterData`
|
||||||
|
*/
|
||||||
|
final case class DetailedCharacterData(a : DetailedCharacterA,
|
||||||
|
b : DetailedCharacterB)
|
||||||
|
(pad_length : Option[Int]) extends ConstructorData {
|
||||||
|
|
||||||
|
override def bitsize : Long = a.bitsize + b.bitsize
|
||||||
|
}
|
||||||
|
|
||||||
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
def apply(bep : Long,
|
def apply(bep : Long,
|
||||||
cep : Long,
|
cep : Long,
|
||||||
|
|
@ -149,7 +193,42 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
firstTimeEvents : List[String],
|
firstTimeEvents : List[String],
|
||||||
tutorials : List[String],
|
tutorials : List[String],
|
||||||
cosmetics : Option[Cosmetics]) : Option[Int]=>DetailedCharacterData = {
|
cosmetics : Option[Cosmetics]) : Option[Int]=>DetailedCharacterData = {
|
||||||
DetailedCharacterData(bep, cep, healthMax, health, armor, staminaMax, stamina, certs, None, implants, Nil, Nil, firstTimeEvents, tutorials, cosmetics)
|
val a = DetailedCharacterA(
|
||||||
|
bep,
|
||||||
|
cep,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
healthMax, health,
|
||||||
|
false,
|
||||||
|
armor,
|
||||||
|
0L,
|
||||||
|
staminaMax, stamina,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0L,
|
||||||
|
List(0,0,0,0,0,0),
|
||||||
|
certs
|
||||||
|
)
|
||||||
|
val b : (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
|
||||||
|
None,
|
||||||
|
implants,
|
||||||
|
Nil,
|
||||||
|
Nil,
|
||||||
|
firstTimeEvents,
|
||||||
|
tutorials,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
None,
|
||||||
|
Nil,
|
||||||
|
Nil,
|
||||||
|
false,
|
||||||
|
cosmetics
|
||||||
|
)
|
||||||
|
(pad_length : Option[Int]) => DetailedCharacterData(a, b(a.bep, pad_length))(pad_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -162,17 +241,19 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
})
|
})
|
||||||
).xmap[ImplantEntry] (
|
).xmap[ImplantEntry] (
|
||||||
{
|
{
|
||||||
case implant :: true :: _ :: HNil =>
|
case implant :: true :: n :: HNil => //initialized (no timer), active/inactive?
|
||||||
ImplantEntry(ImplantType(implant), None) //TODO catch potential NoSuchElementException?
|
val activeBool : Boolean = n != 0
|
||||||
|
ImplantEntry(ImplantType(implant), None, activeBool) //TODO catch potential NoSuchElementException?
|
||||||
|
|
||||||
case implant :: false :: extra :: HNil =>
|
case implant :: false :: extra :: HNil => //unintialized (timer), inactive
|
||||||
ImplantEntry(ImplantType(implant), Some(extra)) //TODO catch potential NoSuchElementException?
|
ImplantEntry(ImplantType(implant), Some(extra), false) //TODO catch potential NoSuchElementException?
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case ImplantEntry(implant, None) =>
|
case ImplantEntry(implant, None, n) => //initialized (no timer), active/inactive?
|
||||||
implant.id :: true :: 0 :: HNil
|
val activeInt : Int = if(n) { 1 } else { 0 }
|
||||||
|
implant.id :: true :: activeInt :: HNil
|
||||||
|
|
||||||
case ImplantEntry(implant, Some(extra)) =>
|
case ImplantEntry(implant, Some(extra), _) => //unintialized (timer), inactive
|
||||||
implant.id :: false :: extra :: HNil
|
implant.id :: false :: extra :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -198,9 +279,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def dcd_list_codec(pad : Int) : Codec[List[DCDExtra1]] = (
|
private def dcd_list_codec(padFunc : (Long)=>Int) : Codec[List[DCDExtra1]] = (
|
||||||
uint8 >>:~ { size =>
|
uint8 >>:~ { size =>
|
||||||
conditional(size > 0, dcd_extra1_codec(pad)) ::
|
conditional(size > 0, dcd_extra1_codec(padFunc(size))) ::
|
||||||
PacketHelpers.listOfNSized(size - 1, dcd_extra1_codec(0))
|
PacketHelpers.listOfNSized(size - 1, dcd_extra1_codec(0))
|
||||||
}
|
}
|
||||||
).xmap[List[DCDExtra1]] (
|
).xmap[List[DCDExtra1]] (
|
||||||
|
|
@ -256,24 +337,98 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
private val dcd_extra2_codec : Codec[DCDExtra2] = (
|
||||||
* The padding value of the first entry in either of two byte-aligned `List` structures.
|
uint(5) ::
|
||||||
* @param implants implant entries
|
uint8L
|
||||||
* @return the pad length in bits `0 <= n < 8`
|
).as[DCDExtra2]
|
||||||
*/
|
|
||||||
private def implantFieldPadding(implants : List[ImplantEntry], varBit : Option[Int] = None) : Int = {
|
|
||||||
val base : Int = 5 //the offset with no implant entries
|
|
||||||
val baseOffset : Int = base - varBit.getOrElse(0)
|
|
||||||
val resultA = if(baseOffset < 0) { 8 - baseOffset } else { baseOffset % 8 }
|
|
||||||
|
|
||||||
var implantOffset : Int = 0
|
private def unkBCodec(padFunc : (Long)=>Int) : Codec[List[String]] = (
|
||||||
implants.foreach({entry =>
|
uint16L >>:~ { size =>
|
||||||
implantOffset += entry.bitsize.toInt
|
conditional(size > 0, PacketHelpers.encodedStringAligned(padFunc(size))) ::
|
||||||
})
|
PacketHelpers.listOfNSized(size - 1, PacketHelpers.encodedString)
|
||||||
val resultB : Int = resultA - (implantOffset % 8)
|
}
|
||||||
if(resultB < 0) { 8 + resultB } else { resultB }
|
).xmap[List[String]] (
|
||||||
|
{
|
||||||
|
case _ :: Some(first) :: Nil :: HNil =>
|
||||||
|
List(first)
|
||||||
|
case _ :: Some(first) :: rest :: HNil =>
|
||||||
|
first +: rest
|
||||||
|
case _ :: None :: _ :: HNil =>
|
||||||
|
List()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case List() =>
|
||||||
|
0 :: None :: Nil :: HNil
|
||||||
|
case contents =>
|
||||||
|
contents.length :: contents.headOption :: contents.tail :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def optToList(opt : Option[Any]) : List[Any] = opt match {
|
||||||
|
case Some(o) => List(o)
|
||||||
|
case None => Nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def displaceByUnk9(start : Option[Int], test : Option[Any], value : Int) : Option[Int] = test match {
|
||||||
|
case Some(_) =>
|
||||||
|
Some(start.getOrElse(0) + value)
|
||||||
|
case None =>
|
||||||
|
start
|
||||||
|
}
|
||||||
|
|
||||||
|
private val displacementPerEntry : List[Int] = List(7, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
def paddingCalculations(contextOffset : Option[Int], implants : List[ImplantEntry], prevLists : List[List[Any]])(currListLen : Long) : Int = {
|
||||||
|
paddingCalculations(3, contextOffset, implants, prevLists)(currListLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
def paddingCalculations(base : Int, contextOffset : Option[Int], implants : List[ImplantEntry], prevLists : List[List[Any]])(currListLen : Long) : Int = {
|
||||||
|
if(currListLen > 0) {
|
||||||
|
//the offset with no implant entries, or bits from context
|
||||||
|
//displacement into next byte of the content field of the first relevant string without padding
|
||||||
|
val baseResult : Int = base + contextOffset.getOrElse(0) + implants.foldLeft(0L)(_ + _.bitsize).toInt
|
||||||
|
|
||||||
|
val displacementResult : Int = (if(prevLists.isEmpty) {
|
||||||
|
baseResult
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//isolate the displacements that are important
|
||||||
|
val sequentialEmptyLists : List[List[Any]] = prevLists.takeWhile(_.isEmpty)
|
||||||
|
val offsetSlice : List[Int] = displacementPerEntry.drop(displacementPerEntry.length - sequentialEmptyLists.length)
|
||||||
|
if(prevLists.length == sequentialEmptyLists.length) { //if all lists are empty, factor in the base displacement
|
||||||
|
baseResult + offsetSlice.sum
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
offsetSlice.sum
|
||||||
|
}
|
||||||
|
}) % 8
|
||||||
|
if(displacementResult != 0) {
|
||||||
|
8 - displacementResult
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0 //if the current list has no length, there's no need to pad it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * The padding value of the first entry in either of two byte-aligned `List` structures.
|
||||||
|
// * @param implants implant entries
|
||||||
|
// * @return the pad length in bits `0 <= n < 8`
|
||||||
|
// */
|
||||||
|
// def implantFieldPadding(implants : List[ImplantEntry], varBit : Option[Int] = None) : Int = {
|
||||||
|
// val base : Int = 5 //the offset with no implant entries
|
||||||
|
// val baseOffset : Int = base - varBit.getOrElse(0)
|
||||||
|
// val resultA = if(baseOffset < 0) { 8 - baseOffset } else { baseOffset % 8 }
|
||||||
|
//
|
||||||
|
// val implantOffset = implants.foldLeft(0L)(_ + _.bitsize).toInt
|
||||||
|
// val resultB : Int = resultA - (implantOffset % 8)
|
||||||
|
// if(resultB < 0) { 8 + resultB } else { resultB }
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Players with certain battle rank will always have a certain number of implant slots.
|
* Players with certain battle rank will always have a certain number of implant slots.
|
||||||
* The encoding requires it.
|
* The encoding requires it.
|
||||||
|
|
@ -292,117 +447,80 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A variant of `ftePadding` where the length of the list has been uncurried.
|
|
||||||
* @see `ftePadding(Int)(Long)`
|
|
||||||
*/
|
|
||||||
private def ftePadding(len : Long, implantPadding : Int) : Int = {
|
|
||||||
//TODO the proper padding length should reflect all variability in the stream prior to this point
|
|
||||||
ftePadding(implantPadding)(len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the padding of the first entry in the first time events list.
|
|
||||||
* @see `ftePadding(Long, Int)`
|
|
||||||
* @param len the length of the first time events list
|
|
||||||
* @param implantPadding the padding that resulted from implant entries
|
|
||||||
* @return the pad length in bits `0 <= n < 8`
|
|
||||||
*/
|
|
||||||
private def ftePadding(implantPadding : Int)(len : Long) : Int = {
|
|
||||||
//TODO the proper padding length should reflect all variability in the stream prior to this point
|
|
||||||
if(len > 0) {
|
|
||||||
implantPadding
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the padding of the first entry in the completed tutorials list.<br>
|
|
||||||
* <br>
|
|
||||||
* The tutorials list follows the first time event list and also contains byte-aligned strings.
|
|
||||||
* If the both lists are populated or empty at the same time, the first entry will not need padding.
|
|
||||||
* If the first time events list is unpopulated, but this list is populated, the first entry will need padding bits.
|
|
||||||
* @see `tutPadding(Long, Long, Int)`
|
|
||||||
* @param len the length of the first time events list
|
|
||||||
* @param implantPadding the padding that resulted from implant entries
|
|
||||||
* @param len2 the length of the tutorial list, curried
|
|
||||||
* @return the pad length in bits `n < 8`
|
|
||||||
*/
|
|
||||||
private def tutPadding(len : Long, implantPadding : Int)(len2 : Long) : Int = {
|
|
||||||
if(len > 0) {
|
|
||||||
0 //automatic alignment from previous List
|
|
||||||
}
|
|
||||||
else if(len2 > 0) {
|
|
||||||
implantPadding //need to align for elements
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
0 //both lists are empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A variant of `tutPadding` where the length of the second list has been uncurried.
|
|
||||||
* @see `tutPadding(Long, Int)(Long)`
|
|
||||||
*/
|
|
||||||
private def tutPadding(len : Long, len2 : Long, implantPadding : Int) : Int = tutPadding(len, implantPadding)(len2)
|
|
||||||
|
|
||||||
def isBR24(bep : Long) : Boolean = bep > 2286230
|
def isBR24(bep : Long) : Boolean = bep > 2286230
|
||||||
|
|
||||||
private val dcd_extra2_codec : Codec[DCDExtra2] = (
|
val a_codec : Codec[DetailedCharacterA] = (
|
||||||
uint(5) ::
|
("bep" | uint32L) ::
|
||||||
uint8L
|
|
||||||
).as[DCDExtra2]
|
|
||||||
|
|
||||||
def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
|
|
||||||
("bep" | uint32L) >>:~ { bep =>
|
|
||||||
("cep" | uint32L) ::
|
("cep" | uint32L) ::
|
||||||
uint32L ::
|
("unk1" | uint32L) ::
|
||||||
uint32L ::
|
("unk2" | uint32L) ::
|
||||||
uint32L ::
|
("unk3" | uint32L) ::
|
||||||
("healthMax" | uint16L) ::
|
("healthMax" | uint16L) ::
|
||||||
("health" | uint16L) ::
|
("health" | uint16L) ::
|
||||||
bool ::
|
("unk4" | bool) ::
|
||||||
("armor" | uint16L) ::
|
("armor" | uint16L) ::
|
||||||
uint32 :: //endianness is important here
|
("unk5" | uint32) :: //endianness?
|
||||||
("staminaMax" | uint16L) ::
|
("staminaMax" | uint16L) ::
|
||||||
("stamina" | uint16L) ::
|
("stamina" | uint16L) ::
|
||||||
uint16L ::
|
//TODO optional 32u-something here; see ps.c: line#1070692
|
||||||
uint(3) ::
|
("unk6" | uint16L) ::
|
||||||
uint32L ::
|
("unk7" | uint(3)) ::
|
||||||
PacketHelpers.listOfNSized(6, uint16L) ::
|
("unk8" | uint32L) ::
|
||||||
("certs" | listOfN(uint8L, CertificationType.codec)) ::
|
("unk9" | PacketHelpers.listOfNSized(6, uint16L)) :: //always 6
|
||||||
optional(bool, "unk1" | uint32L) :: //ask about sample CCRIDER
|
("certs" | listOfN(uint8L, CertificationType.codec))
|
||||||
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
|
).exmap[DetailedCharacterA] (
|
||||||
("unk2" | dcd_list_codec(0)) :: //TODO pad value
|
|
||||||
("unk3" | dcd_list_codec(0)) :: //TODO pad value
|
|
||||||
(("firstTimeEvents" | eventsListCodec(ftePadding(implantFieldPadding(implants, pad_length)))) >>:~ { fte =>
|
|
||||||
("tutorials" | eventsListCodec(tutPadding(fte.length, implantFieldPadding(implants, pad_length)))) >>:~ { _ =>
|
|
||||||
uint32L ::
|
|
||||||
uint32L ::
|
|
||||||
uint32L ::
|
|
||||||
uint32L ::
|
|
||||||
uint32L ::
|
|
||||||
(bool >>:~ { br24 => //BR24+
|
|
||||||
conditional(!br24, dcd_extra2_codec) ::
|
|
||||||
listOfN(uint16L, uint32L) ::
|
|
||||||
listOfN(uint16L, PacketHelpers.encodedString) :: //TODO pad value
|
|
||||||
bool ::
|
|
||||||
conditional(br24, Cosmetics.codec)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
).exmap[DetailedCharacterData] (
|
|
||||||
{
|
{
|
||||||
case o @ (bep :: cep :: 0 :: 0 :: 0 :: hpmax :: hp :: _ :: armor :: 32831L :: stamax :: stam :: 0 :: _ :: _ :: _ :: certs :: unk1 :: implants :: unk2 :: unk3 :: fteList :: tutList :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: cosmetics :: HNil) =>
|
case bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: u6 :: u7 :: u8 :: u9 :: certs :: HNil =>
|
||||||
println(o)
|
Attempt.successful(DetailedCharacterA(bep, cep, u1, u2, u3, healthMax, health, u4, armor, u5, staminaMax, stamina, u6, u7, u8, u9, certs))
|
||||||
Attempt.successful(DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, unk1, implants, unk2, unk3, fteList, tutList, cosmetics)(pad_length))
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, unk1, implants, unk2, unk3, fteList, tutList, cos) =>
|
case DetailedCharacterA(bep, cep, u1, u2, u3, healthMax, health, u4, armor, u5, staminaMax, stamina, u6, u7, u8, u9, certs) =>
|
||||||
|
Attempt.successful(
|
||||||
|
bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: u6 :: u7 :: u8 :: u9 :: certs :: HNil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def b_codec(bep : Long, pad_length : Option[Int]) : Codec[DetailedCharacterB] = (
|
||||||
|
optional(bool, "unk1" | uint32L) :: //ask about sample CCRIDER
|
||||||
|
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
|
||||||
|
("unk2" | dcd_list_codec(paddingCalculations(pad_length, implants, Nil))) >>:~ { unk2 =>
|
||||||
|
("unk3" | dcd_list_codec(paddingCalculations(pad_length, implants, List(unk2)))) >>:~ { unk3 =>
|
||||||
|
("firstTimeEvents" | eventsListCodec(paddingCalculations(pad_length, implants, List(unk3, unk2)))) >>:~ { fte =>
|
||||||
|
("tutorials" | eventsListCodec(paddingCalculations(pad_length, implants, List(fte, unk3, unk2)))) >>:~ { tut =>
|
||||||
|
("unk4" | uint32L) ::
|
||||||
|
("unk5" | uint32L) ::
|
||||||
|
("unk6" | uint32L) ::
|
||||||
|
("unk7" | uint32L) ::
|
||||||
|
("unk8" | uint32L) ::
|
||||||
|
(bool >>:~ { br24 => //BR24+
|
||||||
|
conditional(!br24, "unk9" | dcd_extra2_codec) >>:~ { unk9 =>
|
||||||
|
("unkA" | listOfN(uint16L, uint32L)) ::
|
||||||
|
("unkB" | unkBCodec(
|
||||||
|
paddingCalculations(
|
||||||
|
displaceByUnk9(pad_length, unk9, 5),
|
||||||
|
implants,
|
||||||
|
List(optToList(unk9), tut, fte, unk3, unk2)
|
||||||
|
)
|
||||||
|
)) ::
|
||||||
|
("unkC" | bool) ::
|
||||||
|
conditional(br24, "cosmetics" | Cosmetics.codec)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).exmap[DetailedCharacterB] (
|
||||||
|
{
|
||||||
|
case u1 :: implants :: u2 :: u3 :: fte :: tut :: u4 :: u5 :: u6 :: u7 :: u8 :: _ :: u9 :: uA :: uB :: uC :: cosmetics :: HNil =>
|
||||||
|
Attempt.successful(
|
||||||
|
DetailedCharacterB(u1, implants, u2, u3, fte, tut, u4, u5, u6, u7, u8, u9, uA, uB, uC, cosmetics)(bep, pad_length)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case DetailedCharacterB(u1, implants, u2, u3, fte, tut, u4, u5, u6, u7, u8, u9, uA, uB, uC, cosmetics) =>
|
||||||
val implantCapacity : Int = numberOfImplantSlots(bep)
|
val implantCapacity : Int = numberOfImplantSlots(bep)
|
||||||
val implantList = if(implants.length > implantCapacity) {
|
val implantList = if(implants.length > implantCapacity) {
|
||||||
implants.slice(0, implantCapacity)
|
implants.slice(0, implantCapacity)
|
||||||
|
|
@ -411,14 +529,25 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
recursiveEnsureImplantSlots(implantCapacity, implants)
|
recursiveEnsureImplantSlots(implantCapacity, implants)
|
||||||
}
|
}
|
||||||
val br24 : Boolean = isBR24(bep)
|
val br24 : Boolean = isBR24(bep)
|
||||||
val dcdExtra2Field : Option[DCDExtra2] = if(!br24) {
|
val cos : Option[Cosmetics] = if(br24) { cosmetics } else { None }
|
||||||
Some(DCDExtra2(0, 0))
|
Attempt.successful(
|
||||||
}
|
u1 :: implantList :: u2 :: u3 :: fte :: tut :: u4 :: u5 :: u6 :: u7 :: u8 :: br24 :: u9 :: uA :: uB :: uC :: cos :: HNil
|
||||||
else {
|
)
|
||||||
None
|
}
|
||||||
}
|
)
|
||||||
val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None }
|
|
||||||
Attempt.successful(bep :: cep :: 0L :: 0L :: 0L :: hpmax :: hp :: false :: armor :: 32831L :: stamax :: stam :: 0 :: 0 :: 0L :: List(0, 0, 0, 0, 0, 0) :: certs :: unk1 :: implantList :: unk2 :: unk3 :: fteList :: tutList :: 0L :: 0L :: 0L :: 0L :: 0L :: br24 :: dcdExtra2Field :: Nil :: Nil :: false :: cosmetics :: HNil)
|
def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
|
||||||
|
("a" | a_codec) >>:~ { a =>
|
||||||
|
("b" | b_codec(a.bep, pad_length)).hlist
|
||||||
|
}
|
||||||
|
).exmap[DetailedCharacterData] (
|
||||||
|
{
|
||||||
|
case a :: b :: HNil =>
|
||||||
|
Attempt.successful(DetailedCharacterData(a, b)(pad_length))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case DetailedCharacterData(a, b) =>
|
||||||
|
Attempt.successful(a :: b :: HNil)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue