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:
FateJH 2018-10-11 23:35:26 -04:00
parent 59569f1a7d
commit 3ac0010052
3 changed files with 1361 additions and 610 deletions

View file

@ -288,7 +288,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
("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
("jammered" | bool) ::
optional(bool, "unk3" | uint16L) :: //TODO factor 16u into bitsize
optional(bool, "unk3" | uint16L) ::
("unk4" | uint16L) ::
("name" | PacketHelpers.encodedWideStringAligned(namePadding(name_padding, extra))) ::
("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) =>
val u0Long = if(u0 == 0) {
if(outfit.length == 0) {
u0
}
else {
outfit.length.toLong
}
val u0Long = if(u0 == 0 && outfit.nonEmpty) {
outfit.length.toLong
}
else {
if(outfit.length == 0) {
0L
}
else {
u0
}
} //TODO this is a kludge; unk0 must be non-zero if outfit_name is defined, and zero if empty
u0
} //TODO this is a kludge; unk0 must be (some) non-zero if outfit_name is defined
val (bpackOpt, zipOpt) = if(alt_model) {
val bpackOpt = if(bpack) { Some(true) } else { None }
(bpackOpt, zipline)

View file

@ -12,17 +12,24 @@ import scala.annotation.tailrec
/**
* 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 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`
*/
final case class ImplantEntry(implant : ImplantType.Value,
activation : Option[Int]) extends StreamBitSize {
initialization : Option[Int],
active : Boolean) extends StreamBitSize {
override def bitsize : Long = {
val activationSize = if(activation.isDefined) { 8L } else { 1L }
9L + activationSize
val timerSize = initialization match { case Some(_) => 8L ; case None => 1L }
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>
* <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.
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
* @see `CharacterData`
* @see `CertificationType`
* @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 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
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value;
* 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 firstTimeEvents the list of first time events performed by this avatar;
* 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;
* they become available at battle rank 24;
* these flags do not exist if they are not applicable
* @see `CharacterData`<br>
* `CertificationType`
*/
final case class DetailedCharacterData(bep : Long,
cep : Long,
healthMax : Int,
health : Int,
armor : Int,
staminaMax : Int,
stamina : Int,
certs : List[CertificationType.Value],
unk1 : Option[Long],
implants : List[ImplantEntry],
unk2 : List[DCDExtra1],
unk3 : List[DCDExtra1],
firstTimeEvents : List[String],
tutorials : List[String],
cosmetics : Option[Cosmetics])
(pad_length : Option[Int]) extends ConstructorData {
final case class DetailedCharacterB(unk1 : Option[Long],
implants : List[ImplantEntry],
unk2 : List[DCDExtra1],
unk3 : List[DCDExtra1],
firstTimeEvents : List[String],
tutorials : List[String],
unk4 : Long,
unk5 : Long,
unk6 : Long,
unk7 : Long,
unk8 : Long,
unk9 : Option[DCDExtra2],
unkA : List[Long],
unkB : List[String],
unkC : Boolean,
cosmetics : Option[Cosmetics])
(bep : Long,
pad_length : Option[Int]) extends StreamBitSize {
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
val unk1Size = if(unk1.isDefined) { 32L } else { 0L }
val unk1Size = unk1 match { case Some(_) => 32L ; case None => 0L }
//implant list
var implantSize : Long = 0L
for(entry <- implants) {
implantSize += entry.bitsize
}
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length)
val implantSize : Long = implants.foldLeft(0L)(_ + _.bitsize)
//fte list
val fteLen = firstTimeEvents.size
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding)
for(str <- firstTimeEvents) {
eventListSize += StreamBitSize.stringBitSize(str)
}
//unk2, unk3, TODO padding
val eventListSize : Long = firstTimeEvents.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
//tutorial list
val tutLen = tutorials.size
val tutorialListSize : Long = tutorials.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
val unk2Len = unk2.size
val unk3Len = unk3.size
val unkAllLen = unk2Len + unk3Len
val unk2_3ListSize : Long = 16L + (if(unk2Len > 0) {
val unk2_3ListSize : Long = if(unk2Len > 0) {
unkAllLen * unk2.head.bitsize
}
else if(unk3Len > 0) {
unkAllLen * unk3.head.bitsize
}
else {
0
})
//tutorial list
val tutLen = tutorials.size
var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, implantPadding)
for(str <- tutorials) {
tutorialListSize += StreamBitSize.stringBitSize(str)
0L
}
//character is at least BR24
val br24 = DetailedCharacterData.isBR24(bep)
val extraBitSize : Long = if(br24) { 0L } else { 13L }
//TODO DCDExtra2
//TODO last List of String values, and padding
val unk9Size : Long = if(br24) { 0L } else { 13L }
val unkASize : Long = unkA.length * 32L
val unkBSize : Long = unkB.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
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] {
def apply(bep : Long,
cep : Long,
@ -149,7 +193,42 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
firstTimeEvents : List[String],
tutorials : List[String],
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] (
{
case implant :: true :: _ :: HNil =>
ImplantEntry(ImplantType(implant), None) //TODO catch potential NoSuchElementException?
case implant :: true :: n :: HNil => //initialized (no timer), active/inactive?
val activeBool : Boolean = n != 0
ImplantEntry(ImplantType(implant), None, activeBool) //TODO catch potential NoSuchElementException?
case implant :: false :: extra :: HNil =>
ImplantEntry(ImplantType(implant), Some(extra)) //TODO catch potential NoSuchElementException?
case implant :: false :: extra :: HNil => //unintialized (timer), inactive
ImplantEntry(ImplantType(implant), Some(extra), false) //TODO catch potential NoSuchElementException?
},
{
case ImplantEntry(implant, None) =>
implant.id :: true :: 0 :: HNil
case ImplantEntry(implant, None, n) => //initialized (no timer), active/inactive?
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
}
)
@ -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 =>
conditional(size > 0, dcd_extra1_codec(pad)) ::
conditional(size > 0, dcd_extra1_codec(padFunc(size))) ::
PacketHelpers.listOfNSized(size - 1, dcd_extra1_codec(0))
}
).xmap[List[DCDExtra1]] (
@ -256,24 +337,98 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
}
)
/**
* 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`
*/
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 }
private val dcd_extra2_codec : Codec[DCDExtra2] = (
uint(5) ::
uint8L
).as[DCDExtra2]
var implantOffset : Int = 0
implants.foreach({entry =>
implantOffset += entry.bitsize.toInt
})
val resultB : Int = resultA - (implantOffset % 8)
if(resultB < 0) { 8 + resultB } else { resultB }
private def unkBCodec(padFunc : (Long)=>Int) : Codec[List[String]] = (
uint16L >>:~ { size =>
conditional(size > 0, PacketHelpers.encodedStringAligned(padFunc(size))) ::
PacketHelpers.listOfNSized(size - 1, PacketHelpers.encodedString)
}
).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.
* 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
private val dcd_extra2_codec : Codec[DCDExtra2] = (
uint(5) ::
uint8L
).as[DCDExtra2]
def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
("bep" | uint32L) >>:~ { bep =>
val a_codec : Codec[DetailedCharacterA] = (
("bep" | uint32L) ::
("cep" | uint32L) ::
uint32L ::
uint32L ::
uint32L ::
("healthMax" | uint16L) ::
("health" | uint16L) ::
bool ::
("armor" | uint16L) ::
uint32 :: //endianness is important here
("staminaMax" | uint16L) ::
("stamina" | uint16L) ::
uint16L ::
uint(3) ::
uint32L ::
PacketHelpers.listOfNSized(6, uint16L) ::
("certs" | listOfN(uint8L, CertificationType.codec)) ::
optional(bool, "unk1" | uint32L) :: //ask about sample CCRIDER
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
("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] (
("unk1" | uint32L) ::
("unk2" | uint32L) ::
("unk3" | uint32L) ::
("healthMax" | uint16L) ::
("health" | uint16L) ::
("unk4" | bool) ::
("armor" | uint16L) ::
("unk5" | uint32) :: //endianness?
("staminaMax" | uint16L) ::
("stamina" | uint16L) ::
//TODO optional 32u-something here; see ps.c: line#1070692
("unk6" | uint16L) ::
("unk7" | uint(3)) ::
("unk8" | uint32L) ::
("unk9" | PacketHelpers.listOfNSized(6, uint16L)) :: //always 6
("certs" | listOfN(uint8L, CertificationType.codec))
).exmap[DetailedCharacterA] (
{
case o @ (bep :: cep :: 0 :: 0 :: 0 :: hpmax :: hp :: _ :: armor :: 32831L :: stamax :: stam :: 0 :: _ :: _ :: _ :: certs :: unk1 :: implants :: unk2 :: unk3 :: fteList :: tutList :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: cosmetics :: HNil) =>
println(o)
Attempt.successful(DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, unk1, implants, unk2, unk3, fteList, tutList, cosmetics)(pad_length))
case bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: u6 :: u7 :: u8 :: u9 :: certs :: HNil =>
Attempt.successful(DetailedCharacterA(bep, cep, u1, u2, u3, healthMax, health, u4, armor, u5, staminaMax, stamina, u6, u7, u8, u9, certs))
},
{
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 implantList = if(implants.length > implantCapacity) {
implants.slice(0, implantCapacity)
@ -411,14 +529,25 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
recursiveEnsureImplantSlots(implantCapacity, implants)
}
val br24 : Boolean = isBR24(bep)
val dcdExtra2Field : Option[DCDExtra2] = if(!br24) {
Some(DCDExtra2(0, 0))
}
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)
val cos : Option[Cosmetics] = if(br24) { cosmetics } else { None }
Attempt.successful(
u1 :: implantList :: u2 :: u3 :: fte :: tut :: u4 :: u5 :: u6 :: u7 :: u8 :: br24 :: u9 :: uA :: uB :: uC :: cos :: 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