mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-15 18:10:35 +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
|
||||
(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)
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue