Reading a Struct Within a Struct via PHP's Unpack Function
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
word
I've usedv
which stands for "unsigned short (always 16 bit, little endian byte order). But forint
I've usedl
: "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!
精彩评论