UE4 appBitsCpy函数作用详解

函数作用概括:

  对游戏过程中收到的普通字节流(游戏交互的数据包字节流),在函数CurComponent.Incoming(ProcessedPacketReader);的时候读了这一串字节流的第一个字节的最低位比特,用来判断是否是bHandshakePacket。这一个比特位被读取后,自然要丢弃这一比特位,重新组装这一串字节流,然后将这一串字节流转交给其他函数处理。
  因此对于游戏过程中的普通字节流(会读取第一个字节的最低位比特),函数appBitsCpy的作用是重新组装被读取过一个比特的字节流并将新组装的字节流复制到新的空间中。
  重新组装字节流的规则是(针对第一个字节最低位比特被读取的字节流):丢弃第一个字节的最后一位,然后将下一个元素的最后一位移到第一个元素的第一位。接下来循环将下一个元素的最后一位移到前一个元素的最高位。
  而在这个过程中分成了三个步骤进行:
  第一个步骤:Lead-in:舍弃源空间被读取的第一个字节的最后一位比特,将第二个字节的最后一位移到第一个字节的第一位。最后将新组装的第一个字节复制到新的空间Dest内。
  第二个步骤:Inner-loop:循环执行上述的操作。将源空间内的当前元素的下一个元素的最后一个比特位移到当前元素的最高位,并将当前元素复制到新的空间Dest内。
  第三个步骤:Lead-out:处理源空间最后一个元素的复制。
  这三个步骤都会将源空间的每个元素按比特位占位的形式复制到一个变量BitAccu上,从BitAccu中取最低8比特复制到新的空间内。

抓包详细分析流程和代码:

调用传入的参数:

appBitsCpy( uint8* Dest, int32 DestBit, uint8* Src, int32 SrcBit, int32 BitCount );

appBitsCpy(目的空间指针,从目的位置0开始, 源空间指针,源比特偏移, 源数据占据比特数 );

抓包得到源数据比特流为:
0 1 2 3 4 5 6 7 8 9
10000000 01000000 10011011 01000111 11111110 11111111 11111111 11111111 01111111 00100011
128、64、155、71、254、255、255、255、127、35
 
最终重新组装的字节流:
01000000 10100000 11001101 00100011 11111111 11111111 11111111 11111111 10111111 00010001
64、160、205、35、255、255、255、255、191、17
 
传入参数SrcBit=1,说明第一个字节的最后一个比特位被读取了用来判断是否是bHandshakePacket,BitCount=77。也就是说下标为9的元素只有5个有效比特位。
 

几个需要用到的变量解析:

//因为DestBit总是0,因此DestIndex总是从0开始;
uint32 DestIndex = DestBit/8;

//由名字可知只对源数据第一个元素作用的数字
//FirstSrcMask总是拿来和其他数字做与运算(&)
uint32 FirstSrcMask  = 0xFF << ( DestBit & 7);  

//如果不能被8整除,那么LastDest是最后一个有效元素的下标;
//如果能被8整除,那么LastDest是最后一个有效元素下标的下一个
uint32 LastDest = ( DestBit+BitCount )/8; 

//BitCount&7如果不等于0:说明有向上取整,值是占据N个字节多BitCount&7位
//例如:BitCount=9,那么BitCount&7=1,说明占据1个字节多1位
//多1位就将0xFF左移1位:1111 1110
//由名字可知:只对源数据的最后一个元素做Mask
uint32 LastSrcMask   = 0xFF << ((DestBit + BitCount) & 7);

//SrcBit是源数据当前的偏移位置,偏移后在源数据数组的哪个下标上
uint32 SrcIndex = SrcBit/8;

//源数据最后一个元素的下标;
//如果不能被8整除,那么LastSrc是最后一个有效元素的下标;
//如果能被8整除,那么LastSrc是最后一个有效元素下标的下一个;
uint32 LastSrc    = ( SrcBit+BitCount )/8;  

//如果SrcBit等于0,ShiftCount=0
//如果SrcBit等于1,ShiftCount=-1
//ShiftCount为负数用来指示第一个字节几位被读取过,最后ShiftCount+=8就是代表还剩多少位有效
int32   ShiftCount = (DestBit & 7) - (SrcBit & 7); 

//遍历所有目的数据的数组需要循环的次数
int32   DestLoop = LastDest-DestIndex;

//遍历所有源数据的数组需要循环的次数 
int32   SrcLoop = LastSrc -SrcIndex;  
uint32 FullLoop;

//BitAccu是对源空间内元素比特位的平铺,假如源空间内有两个元素:100(01100100) 、90(01011010),
//那么BitAccu的可能比特位为:01100100 01011010,值为: 25690
//实际上BitAccu会将两个元素逆序,这里只是作BitAccu可能形式的例子;
uint32 BitAccu;
在这组抓包的例子中这些值分别等于:
DestIndex=0,
FirstSrcMask=255,
LastDest=9,
LastSrcMask=8160=00011111 11100000(因为最后一个元素只有5位有效,所以左移了5位)
SrcIndex=0,
LastSrc=9,
ShiftCount=-1,
DestLoop=9,
SrcLoop=9
 

进行第一个步骤:Lead-in

  舍弃被读取的第一个字节的最后一位比特,将第二个字节的最后一位移到第一个字节的第一位。最后将第一个字节复制到新的空间Dest内。

  将数据复制到Dest都是通过先构造BitAccu变量,然后将低8位复制到新空间内。
 

进行第二个步骤:Inner-loop

  循环执行上述的操作。将源空间内的当前元素的下一个元素的最后一个比特位移到当前元素的最高位,并将当前元素复制到新的空间Dest内。

  第一次循环,是将源数据的第三个元素右移15位放到了BitAccu=00000000 00000000 00100000 01000000上;然后将BitAccu右移8位丢弃,将源数据的第二个元素(橙色)复制到Dest空间上。
  同理到了最后一次循环,是将源数据的第10个元素(即最后一个),右移BitAccu8位丢弃,将第9个元素复制到Dest空间上。
  因此最后一次循环出来以后,BitAccu包含了最后一个元素和倒数第二个元素,此时BitAccu=00010001 10111111,其中源空间最后一个元素(即BitAccu的高8位)还未被复制到Dest空间上。
 

进行第三个步骤:Lead-Out

  最后一次循环出来以后,需要先判断是不是源空间内最后一个元素是不是已经在BitAccu上了。判断的办法如代码所示:(SrcBit+BitCount-1)/8==SrcIndex
  如果不等于SrcIndex,将BitAccu右移8位,舍弃BitAccu已经拷贝到Dest新空间的低8位,最后将剩余的8位拷到Dest新空间内。此时源空间所有的元素都经过重新组装并拷贝到了新空间Dest内。appBitsCpy函数执行完毕。
  如果等于(SrcBit+BitCount-1)/8==SrcIndex,说明源空间最后一个元素还没有在BitAccu上,因此再做一次BitAccu = ( ( (uint32)Src[SrcIndex] << ShiftCount ) + (BitAccu)) >> 8;最后再将源空间最后一个元素拷贝到Dest新空间内。
 

后续:

  appBitsCpy在多个地方都有用到,后续会继续分析它在其他地方的用途。
posted @ 2020-07-12 19:34  只取一瓢饮  阅读(612)  评论(0编辑  收藏  举报