开发者

Reading a Struct Within a Struct via PHP's Unpack Function

开发者 https://www.devze.com 2022-12-19 21:27 出处:网络
I want to know how to read a struct within a struct via php\'s unpack function. When I get an IS_MCI packet, I check it\'s Type to make sure it\'s equal to ISP_MCI, and then I check NumC to find out h

I want to know how to read a struct within a struct via php's unpack function. When I get an IS_MCI packet, I check it's Type to make sure it's equal to ISP_MCI, and then I check NumC to find out how many CompCar structs there are within this packet. The problem is trying to unpack these cont开发者_运维技巧ents into an array via a single function. I always get a undefined offset. So, i'm looking for some fresh eyes on the matter.

How would you handle this packet?

The struct in question is this:

struct IS_MCI // Multi Car Info - if more than 8 in race then more than one of these is sent
{
    byte    Size;       // 4 + NumC * 28
    byte    Type;       // ISP_MCI
    byte    ReqI;       // 0 unless this is a reply to an TINY_MCI request
    byte    NumC;       // number of valid CompCar structs in this packet

    CompCar Info[8];    // car info for each player, 1 to 8 of these (NumC)
};

struct CompCar // Car info in 28 bytes - there is an array of these in the MCI (below)
{
    word    Node;       // current path node
    word    Lap;        // current lap
    byte    PLID;       // player's unique id
    byte    Position;   // current race position : 0 = unknown, 1 = leader, etc...
    byte    Info;       // flags and other info - see below
    byte    Sp3;
    int     X;          // X map (65536 = 1 metre)
    int     Y;          // Y map (65536 = 1 metre)
    int     Z;          // Z alt (65536 = 1 metre)
    word    Speed;      // speed (32768 = 100 m/s)
    word    Direction;  // direction of car's motion : 0 = world y direction, 32768 = 180 deg
    word    Heading;    // direction of forward axis : 0 = world y direction, 32768 = 180 deg
    short   AngVel;     // signed, rate of change of heading : (16384 = 360 deg/s)
};


$msg = 
   chr(0x20) // Size = 32 (4+1*28)
  . chr(0x1) // Type = 1
  . chr(0x0) // ReqI=0
  . chr(0x1) // NumC=1
    . chr(0x1) . chr(0x0) // node=1
    . chr(0x2) . chr(0x0)  // lap=2
    . chr(0x3) // puid=3
    . chr(0x5) // pos=5
    . chr(0x10) // info=16
    . chr(0x0) //sp3=0
    . chr(0x0) . chr(0x0) . chr(0x1) . chr(0x0) // x=65536
    . chr(0x0) . chr(0x0) . chr(0x2) . chr(0x0) // y=65536*2
    . chr(0x0) . chr(0x0) . chr(0x3) . chr(0x0)  // z=65536*3
    . chr(0x0) . chr(0x20) // speed=8192
    . chr(0x0) . chr(0x10) // dir=4096
    . chr(0x0) . chr(0x8) // heading=2048
    . chr(0x0) . chr(0x4) // AngVel=1024
;

$IS_MCI = unpack('CSize', $msg);
if ( strlen($msg) < $IS_MCI['Size'] ) {
  die("not enough data");
}
$IS_MCI += unpack('CType/CReqI/CNumC', substr($msg, 1));
$IS_MCI['Info'] = array();

for($i=0; $i<$IS_MCI['NumC']; $i++) {
  $data = substr($msg, 4+($i*28), 28);
  $IS_MCI['Info'][] = unpack('vNode/vLap/CPLID/CPosition/CInfo/CSp3/lX/lY/lZ/vSpeed/vDirection/vHeading/sAngVel', $data);
}
print_r($IS_MCI);

prints

Array
(
    [Size] => 32
    [Type] => 1
    [ReqI] => 0
    [NumC] => 1
    [Info] => Array
        (
            [0] => Array
                (
                    [Node] => 1
                    [Lap] => 2
                    [PLID] => 3
                    [Position] => 5
                    [Info] => 16
                    [Sp3] => 0
                    [X] => 65536
                    [Y] => 131072
                    [Z] => 196608
                    [Speed] => 8192
                    [Direction] => 4096
                    [Heading] => 2048
                    [AngVel] => 1024
                )

        )

)

Now, that code makes some assumptions that you might not want to take for granted (i.e. add a lot more error/read-data handling).

  • It assumes the packet ($msg) has been completely read before the code runs. You might want to read only the parts you currently need (no need for substr() then). Or least be prepared that the message can arrive in several chunks.
  • It also takes the size/num parameters for granted, i.e. it doesn't check if the values are feasible and enough data is available. That's definitely something you have to change. Size must be between 0...228, NumC must be between 0...8 and both values must fit together and so on.
  • Also take a closer look at the format identifiers I've used in unpack(). For wordI've used v which stands for "unsigned short (always 16 bit, little endian byte order). But for int I've used l: "signed long (always 32 bit, machine byte order)". That's ok on my machine. But search the documentation of the protocol for the endianness of the data.

The testdata in $msg has been taken from the result of

__declspec(align(1)) struct CompCar // Car info in 28 bytes - there is an array of these in the MCI (below)
{
    word    Node;       // current path node
    word    Lap;        // current lap
    byte    PLID;       // player's unique id
    byte    Position;   // current race position : 0 = unknown, 1 = leader, etc...
    byte    Info;       // flags and other info - see below
    byte    Sp3;
    int     X;          // X map (65536 = 1 metre)
    int     Y;          // Y map (65536 = 1 metre)
    int     Z;          // Z alt (65536 = 1 metre)
    word    Speed;      // speed (32768 = 100 m/s)
    word    Direction;  // direction of car's motion : 0 = world y direction, 32768 = 180 deg
    word    Heading;    // direction of forward axis : 0 = world y direction, 32768 = 180 deg
    short   AngVel;     // signed, rate of change of heading : (16384 = 360 deg/s)
};

__declspec(align(1)) struct IS_MCI // Multi Car Info - if more than 8 in race then more than one of these is sent
{
    byte    Size;       // 4 + NumC * 28
    byte    Type;       // ISP_MCI
    byte    ReqI;       // 0 unless this is a reply to an TINY_MCI request
    byte    NumC;       // number of valid CompCar structs in this packet

    CompCar Info[1];    // example: one element, fixed
};

int _tmain(int argc, _TCHAR* argv[])
{
  struct IS_MCI mci = {
    32, 1, 0, 1,
    { 1, 2, 3, 5, 16, 0, 65536, 65536*2, 65536*3, 8192, 4096, 2048, 1024 }
  };

  WSADATA wsaData;
  WORD wVersionRequested = MAKEWORD( 2, 2 );
   int err = WSAStartup( wVersionRequested, &wsaData );
  if ( err != 0 ) {
      /* Tell the user that we could not find a usable */
      /* WinSock DLL.                                  */
      return 1;
  }

  SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  sockaddr_in addr; 
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
  addr.sin_port = htons( 8081 );
  if ( 0!=connect( s, (SOCKADDR*) &addr, sizeof(addr) ) ) {
    printf("%X ", WSAGetLastError());
    return 0;
  }
  send(s, (const char*)&mci, sizeof(mci), 0);
  shutdown(s, SD_BOTH);
  closesocket(s);
  return 0;
}


I'm using this:


class IS_MCI extends ISP {
        public $Size;
        public $Type = ISP_MCI;
        public $ReqI;
        public $NumC;

        public function IS_MCI($data, &$CompCar) {
                $up = unpack('CSize/CType/CReqI/CNumC', $data);
                $this->Size = $up['Size'];
                $this->ReqI = $up['ReqI'];
                $this->NumC = $up['NumC'];

                $temp = array();

                $p = 4;
                for ($i = 0; $i NumC; $i++) {
                        $up2 = unpack('SNode/SLap/CPLID/CPosition/CInfo/CSp3/IX/IY/IZ/SSpeed/SDirection/SHeading/sAngVel', substr($data, $p, 28));
                        $temp[] = new CompCar($up2['Node'],$up2['Lap'],$up2['PLID'],$up2['Position'],$up2['Info'],$up2['Sp3'],$up2['X'],$up2['Y'],$up2['Z'],$
                        $p += 28;
                }
                $CompCar = $temp;
        }
}

And CompCar class:


class CompCar {
        public $xNode;          // current path node
        public $Lap;            // current lap
        public $PLID;           // player's unique id
        public $Position;       // current race position : 0 = unknown, 1 = leader, etc...
        public $Info;           // flags and other info - see below
        public $Sp3;
        public $X;              // X map (65536 = 1 metre)
        public $Y;              // Y map (65536 = 1 metre)
        public $Z;              // Z alt (65536 = 1 metre)
        public $Speed;          // speed (32768 = 100 m/s)
        public $Direction;      // direction of car's motion : 0 = world y direction, 32768 = 180 deg
        public $Heading;        // direction of forward axis : 0 = world y direction, 32768 = 180 deg
        public $AngVel;         // signed, rate of change of heading : (16384 = 360 deg/s)

        public $SpeedKPH;       // speed in kph
        public $SpeedMPH;       // speed in mph
        public $DirectionC;     // Direction calculated to degrees
        public $HeadingC;       // Heading calculated to degrees
        public $AngVelC;        // Calculated

        // ADDED:
        public $SpeedMS;                // speed in mps

        public function __construct($xNode,$Lap,$PLID,$Position,$Info,$Sp3,$X,$Y,$Z,$Speed,$Direction,$Heading,$AngVel) {
                $this->xNode = $xNode;
                $this->Lap = $Lap;
                $this->PLID = $PLID;
                $this->Position = $Position;
                $this->Info = $Info;
                $this->Sp3 = $Sp3;
                $this->X = $X;
                $this->Y = $Y;
                $this->Z = $Z;
                $this->Speed = $Speed;
                $this->Direction = $Direction;
                $this->Heading = $Heading;
                $this->AngVel = $AngVel;

                $this->doCalcs();
        }

        private function doCalcs() {
                // Speed Calc
                $old = $this->Speed;
                $this->SpeedKPH = ($old * (100 / 32768)) * 3.6;
                $this->SpeedMPH = $this->SpeedKPH * 0.6215;

                $this->SpeedKPH = floor($this->SpeedKPH);
                $this->SpeedMPH = floor($this->SpeedMPH);
                $this->SpeedMS = $this->SpeedKPH/3.6;

                // Direction
                $this->DirectionC = CompCar::degrees($this->Direction);

                // Heading
                $this->HeadingC = CompCar::degrees($this->Heading);

                // Angle Calcs
                $this->AngVelC = $this->AngVel * 180 / 8192;
        }

        public static function degrees($input) {
                $input = $input / 65535 * 360;
                //$input = 360 - floor($input);
                $input = floor(360 - $input);
                return $input;
        }

}

And everything it's working fine!

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号