Is getting the version of a exe possible with php? I'd like to print the version of a file that can be downloaded...
Windows exe and php is running on linux server
I wanted the same thing, so I coded this:
It returns FALSE, if it can't get the version info, or an ARRAY of four elements with file version fields (numbers, which are separated by .) It works only for 32-bit PE files (as I had no need for other formats).
function GetFileVersion($FileName)
{
$handle = fopen($FileName, 'rb');
if(!$handle)
{
return FALSE;
}
$Header = fread($handle, 64);
if(substr($Header, 0, 2) != 'MZ')
{
return FALSE;
}
$PEOffset = unpack("V", substr($Header, 60, 4));
if($PEOffset[1] < 64)
{
return FALSE;
}
fseek($handle, $PEOffset[1], SEEK_SET);
$Header = fread($handle, 24);
if(substr($Header, 0, 2) != 'PE')
{
return FALSE;
}
$Machine = unpack("v", substr($Header, 4, 2));
if($Machine[1] != 332)
{
return FALSE;
}
$NoSections = unpack("v", substr($Header, 6, 2));
$OptHdrSize = unpack("v", substr($Header, 20, 2));
fseek($handle, $OptHdrSize[1], SEEK_CUR);
$ResFound = FALSE;
for ($x = 0; $x < $NoSections[1]; $x++)
{
$SecHdr = fread($handle, 40);
if (substr($SecHdr, 0, 5) == '.rsrc')
{
$ResFound = TRUE;
break;
}
}
if(!$ResFound)
{
return FALSE;
}
$InfoVirt = unpack("V", substr($SecHdr, 12, 4));
$InfoSize = unpack("V", substr($SecHdr, 16, 4));
$InfoOff = unpack("V", substr($SecHdr, 20, 4));
fseek($handle, $InfoOff[1], SEEK_SET);
$Info = fread($handle, $InfoSize[1]);
$NumDirs = unpack("v", substr($Info, 14, 2));
$InfoFound = FALSE;
for ($x = 0; $x <$NumDirs[1]; $x++)
{
$Type = unpack("V", substr($Info, ($x * 8) + 16, 4));
if($Type[1] == 16)
{
//FILEINFO resource
$InfoFound = TRUE;
$SubOff = unpack("V", substr($Info, ($x * 8) + 20, 4));
break;
}
}
if (!$InfoFound)
{
return FALSE;
}
$SubOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", substr($Info, $SubOff[1] + 20, 4)); //offset of first FILEINFO
$InfoOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", substr($Info, $InfoOff[1] + 20, 4)); //offset to data
$DataOff = unpack("V", substr($Info, $InfoOff[1], 4));
$DataSize = unpack("V", substr($Info, $InfoOff[1] + 4, 4));
$CodePage = unpack("V", substr($Info, $InfoOff[1] + 8, 4));
$DataOff[1] -= $InfoVirt[1];
$Version = unpack("v4", substr($Info, $DataOff[1] + 48, 8));
$x = $Version[2];
$Version[2] = $Version[1];
$Version[1] = $x;
$x = $Version[4];
$Version[4] = $Version[3];
$Version[3] = $x;
return $Version;
}
I recently moved our hosting from Windows to Linux. This was quite easy to do with VBScript given the Microsoft objects, but on Linux and PHP I couldn't find anything. We wrote this function in PHP to scan the .exe file and extract the "Product Version" that a VB.Net application had in it. You can change the $key to be whatever string you can find that has the version info and a null terminator.
Note that this scans the files in 64k chunks looking for the $key string. If you have a large .exe, it may take a few seconds. My .exe is 52k so it is nearly instant. If you have a larger exe, you can change the scan.
<?php
function get_product_version($file_name)
{
$key = "P\x00r\x00o\x00d\x00u\x00c\x00t\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00\x00\x00";
$fptr = fopen($file_name, "rb");
$data = "";
while (!feof($fptr))
{
$data .= fread($fptr, 65536);
if (strpos($data, $key)!==FALSE)
break;
$data = substr($data, strlen($data)-strlen($key));
}
fclose($fptr);
if (strpos($data, $key)===FALSE)
return "";
$pos = strpos($data, $key)+strlen($key);
$version = "";
for ($i=$pos; $data[$i]!="\x00"; $i+=2)
$version .= $data[$i];
return $version;
}
echo get_product_version("/path_to_file/foo.exe");
?>
On a win32 machine you can use the COM extension and FileSystemObject.GetFileVersion() method to retrieve the version info.
e.g.
$path = getenv('SystemRoot').'\\NOTEPAD.EXE';
$fso = new COM('Scripting.FileSystemObject');
echo $path, ' : ', $fso->GetFileVersion($path);
prints (on my machine) C:\WINDOWS\NOTEPAD.EXE : 5.1.2600.5512
I am assuming you're not on Windows, and you mean the Version information that can be stored in Windows executables, and pops up in the properties dialog for such a file in Windows Explorer.
This information seems to be stored in the VS_VERSION_INFO block of an executable (see for example this question). I don't know any tool that extracts this information in a simple way, not even on Windows itself.
There seem to be several ways to get hold of this information via the Windows API (See a Perl example here) but I can't see any approach that works "from scratch" by just parsing the executable.
If you dig around a bit, you might be able to find a file format description that explains how to read the VS_VERSION_INFO information from an EXE file. Be prepared for a lot of work to get this to work reliably, though.
Be prepared to invest a lot of time and effort if you want to do this.
I combined the answers together and added the NamedDirs fix. Also want to highlight not to use the code by NeuD, the offset should remain 16; 14 is definitely wrong. Hope it helps someone.
function GetFileVersion($FileName)
{
return GetValueOfSeeking($FileName, "FileVersion");
}
function GetValueOfSeeking($FileName, $seeking)
{
$handle = fopen($FileName, 'rb');
if (!$handle) return FALSE;
$Header = fread($handle, 64);
if (substr($Header, 0, 2) != 'MZ') return FALSE;
$PEOffset = unpack("V", substr($Header, 60, 4));
if ($PEOffset[1]<64) return FALSE;
fseek($handle, $PEOffset[1], SEEK_SET);
$Header = fread ($handle, 24);
if (substr($Header, 0, 2) != 'PE') return FALSE;
$Machine = unpack("v", substr($Header, 4, 2));
if ($Machine[1] != 332) return FALSE;
$NoSections = unpack("v", substr($Header, 6, 2));
$OptHdrSize = unpack("v", substr($Header, 20, 2));
fseek($handle, $OptHdrSize[1], SEEK_CUR);
$ResFound = FALSE;
for ($x = 0; $x < $NoSections[1]; $x++)
{
//$x fixed here
$SecHdr = fread($handle, 40);
if (substr($SecHdr, 0, 5) == '.rsrc')
{
//resource section
$ResFound = TRUE;
break;
}
}
if (!$ResFound) return FALSE;
$InfoVirt = unpack("V", substr($SecHdr, 12, 4));
$InfoSize = unpack("V", substr($SecHdr, 16, 4));
$InfoOff = unpack("V", substr($SecHdr, 20, 4));
fseek($handle, $InfoOff[1], SEEK_SET);
$Info = fread($handle, $InfoSize[1]);
$NumNamedDirs = unpack("v",substr($Info, 12, 2));
$NumDirs = unpack("v", substr($Info, 14, 2));
$InfoFound = FALSE;
for ($x = 0; $x < ($NumDirs[1] + $NumNamedDirs[1]); $x++)
{
$Type = unpack("V", substr($Info, ($x * 8) + 16, 4));
if($Type[1] == 16)
{
//FILEINFO resource
$InfoFound = TRUE;
$SubOff = unpack("V", substr($Info, ($x * 8) + 20, 4));
break;
}
}
if (!$InfoFound) return FALSE;
if (0)
{
$SubOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", substr($Info, $SubOff[1] + 20, 4)); //offset of first FILEINFO
$InfoOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", substr($Info, $InfoOff[1] + 20, 4)); //offset to data
$DataOff = unpack("V", substr($Info, $InfoOff[1], 4));
$DataSize = unpack("V", substr($Info, $InfoOff[1] + 4, 4));
$CodePage = unpack("V", substr($Info, $InfoOff[1] + 8, 4));
$DataOff[1] -= $InfoVirt[1];
$Version = unpack("v4", substr($Info, $DataOff[1] + 48, 8));
$x = $Version[2];
$Version[2] = $Version[1];
$Version[1] = $x;
$x = $Version[4];
$Version[4] = $Version[3];
$Version[3] = $x;
return $Version;
}
//view data...
//echo print_r(explode("\x00\x00\x00", $Info));
// could prolly substr on VS_VERSION_INFO
$encodedKey = implode("\x00",str_split($seeking));
$StartOfSeekingKey = strpos($Info, $encodedKey);
if ($StartOfSeekingKey !== false) {
$ulgyRemainderOfData = substr($Info, $StartOfSeekingKey);
$ArrayOfValues = explode("\x00\x00\x00", $ulgyRemainderOfData);
// the key your are seeking is 0, where the value is one
return trim($ArrayOfValues[1]);
}
return false;
}
Elaborating on the answer Toni provided
One of the projects at work had a custom attribute baked into that resource section. On a linux machine we didnt want to install perl to use the windows function to get the values.
Instead one can use this variant of the GetFileVersion to get any value you are seeking. (FileVersion, ProductVersion, ProductName, CompanyName, CompanyWebsite)
It should return false for values that dont exist, and it functions rather quick.
$handle=fopen($FileName,'rb');
if (!$handle) return FALSE;
$Header=fread ($handle,64);
if (substr($Header,0,2)!='MZ') return FALSE;
$PEOffset=unpack("V",substr($Header,60,4));
if ($PEOffset[1]<64) return FALSE;
fseek($handle,$PEOffset[1],SEEK_SET);
$Header=fread ($handle,24);
if (substr($Header,0,2)!='PE') return FALSE;
$Machine=unpack("v",substr($Header,4,2));
if ($Machine[1]!=332) return FALSE;
$NoSections=unpack("v",substr($Header,6,2));
$OptHdrSize=unpack("v",substr($Header,20,2));
fseek($handle,$OptHdrSize[1],SEEK_CUR);
$ResFound=FALSE;
for ($x=0;$x<$NoSections[1];$x++) {
$SecHdr=fread($handle,40);
if (substr($SecHdr,0,5)=='.rsrc') { //resource section
$ResFound=TRUE;
break;
}
}
if (!$ResFound) return FALSE;
$InfoVirt=unpack("V",substr($SecHdr,12,4));
$InfoSize=unpack("V",substr($SecHdr,16,4));
$InfoOff=unpack("V",substr($SecHdr,20,4));
fseek($handle,$InfoOff[1],SEEK_SET);
$Info=fread($handle,$InfoSize[1]);
$NumDirs=unpack("v",substr($Info,14,2));
$InfoFound=FALSE;
for ($x=0;$x<$NumDirs[1];$x++) {
$Type=unpack("V",substr($Info,($x*8)+16,4));
if($Type[1]==16) { //FILEINFO resource
$InfoFound=TRUE;
$SubOff=unpack("V",substr($Info,($x*8)+20,4));
//echo $Info;
break;
}
}
if (!$InfoFound) return FALSE;
// i bypassed this, but if you knew the layout you could prolly do a little better then $ulgyRemainderOfData
/*
$SubOff[1]&=0x7fffffff;
$InfoOff=unpack("V",substr($Info,$SubOff[1]+20,4)); //offset of first FILEINFO
$InfoOff[1]&=0x7fffffff;
$InfoOff=unpack("V",substr($Info,$InfoOff[1]+20,4)); //offset to data
$DataOff=unpack("V",substr($Info,$InfoOff[1],4));
$DataSize=unpack("V",substr($Info,$InfoOff[1]+4,4));
$CodePage=unpack("V",substr($Info,$InfoOff[1]+8,4));
$DataOff[1]-=$InfoVirt[1];
$Version=unpack("v4",substr($Info,$DataOff[1]+48,8));
// swap 1-2 3-4 / endian ecoding issue
$x=$Version[2];
$Version[2]=$Version[1];
$Version[1]=$x;
$x=$Version[4];
$Version[4]=$Version[3];
$Version[3]=$x;
return $Version;
*/
//view data...
//echo print_r(explode("\x00\x00\x00", $Info));
// could prolly substr on VS_VERSION_INFO
$encodedKey = implode("\x00",str_split($seeking));
$StartOfSeekingKey = strpos($Info, $encodedKey);
if ($StartOfSeekingKey !== false) {
$ulgyRemainderOfData = substr($Info, $StartOfSeekingKey);
$ArrayOfValues = explode("\x00\x00\x00", $ulgyRemainderOfData);
// the key your are seeking is 0, where the value is one
return trim($ArrayOfValues[1]);
}
return false;
}
$fileVersion = GetValueOfSeeking("./the/path/to/some.exe", 'FileVersion');
$myAttribute = GetValueOfSeeking("./the/path/to/some.exe", 'CustomAttribute');
For me to get the code from Toni to work I had to do a small change:
function GetFileVersion($FileName) {
$handle=fopen($FileName,'rb');
if (!$handle) return FALSE;
$Header=fread ($handle,64);
if (substr($Header,0,2)!='MZ') return FALSE;
$PEOffset=unpack("V",substr($Header,60,4));
if ($PEOffset[1]<64) return FALSE;
fseek($handle,$PEOffset[1],SEEK_SET);
$Header=fread ($handle,24);
if (substr($Header,0,2)!='PE') return FALSE;
$Machine=unpack("v",substr($Header,4,2));
if ($Machine[1]!=332) return FALSE;
$NoSections=unpack("v",substr($Header,6,2));
$OptHdrSize=unpack("v",substr($Header,20,2));
fseek($handle,$OptHdrSize[1],SEEK_CUR);
$ResFound=FALSE;
for ($x=0;$x<$NoSections[1];$x++) { //$x fixed here
$SecHdr=fread($handle,40);
if (substr($SecHdr,0,5)=='.rsrc') { //resource section
$ResFound=TRUE;
break;
}
}
if (!$ResFound) return FALSE;
$InfoVirt=unpack("V",substr($SecHdr,12,4));
$InfoSize=unpack("V",substr($SecHdr,16,4));
$InfoOff=unpack("V",substr($SecHdr,20,4));
fseek($handle,$InfoOff[1],SEEK_SET);
$Info=fread($handle,$InfoSize[1]);
$NumDirs=unpack("v",substr($Info,16,2));
$InfoFound=FALSE;
for ($x=0;$x<$NumDirs[1];$x++) {
$Type=unpack("V",substr($Info,($x*8)+16,4));
if($Type[1]==16) { //FILEINFO resource
$InfoFound=TRUE;
$SubOff=unpack("V",substr($Info,($x*8)+20,4));
break;
}
}
if (!$InfoFound) return FALSE;
$SubOff[1]&=0x7fffffff;
$InfoOff=unpack("V",substr($Info,$SubOff[1]+20,4)); //offset of first FILEINFO
$InfoOff[1]&=0x7fffffff;
$InfoOff=unpack("V",substr($Info,$InfoOff[1]+20,4)); //offset to data
$DataOff=unpack("V",substr($Info,$InfoOff[1],4));
$DataSize=unpack("V",substr($Info,$InfoOff[1]+4,4));
$CodePage=unpack("V",substr($Info,$InfoOff[1]+8,4));
$DataOff[1]-=$InfoVirt[1];
$Version=unpack("v4",substr($Info,$DataOff[1]+48,8));
$x=$Version[2];
$Version[2]=$Version[1];
$Version[1]=$x;
$x=$Version[4];
$Version[4]=$Version[3];
$Version[3]=$x;
return $Version;
}
Where I only changed 14 to 16 in the line
$NumDirs=unpack("v",substr($Info,16,2));
Maybe this has something to do with what j_schultz already added in the comments.
I wrote a small code that will pull the file version directly from the executable.
PHP:
<?php
function exeparser_fileversion($file) {
$parser_model = array('begin'=>"F\x00i\x00l\x00e\x00V\x00e\x00r\x00s\x00i\x00o\x00n",'end'=>"\x00\x00\x00");
if (file_exists($file) && is_readable($file)) {
$version = file_get_contents($file);
$version = explode($parser_model['begin'], $version);
$version = explode($parser_model['end'], $version[1]);
$version = str_replace("\x00", null, $version[1]);
return ((!empty($version) ? "\x1b[32m$file version: $version\x1b[0m" : "\x1b[31mNo version\x1b[0m"));
} else {
print "\x1b[31m".(is_dir($file) ? "Specified path points to a directory, not a file." : "The specified path to the file may not exist or is not a file at all.")."\x1b[0m";
return false;
}
}
#print exeparser_fileversion($argv[1]);
?>
Windows command (optional):
#php "%~dp0\exeparser.php" %*
Result:
Related
I get some PHP code, but I don't understand why it's written this way. Can somebody please explain it to me?
function unmask($text)
{
$length = ord($text[1]) & 127;
if ($length == 126) {
$masks = substr($text, 4, 4);
$data = substr($text, 8);
} elseif ($length == 127) {
$masks = substr($text, 10, 4);
$data = substr($text, 14);
} else {
$masks = substr($text, 2, 4);
$data = substr($text, 6);
}
$text = "";
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i % 4];
}
return $text;
}
Specifically, what does:
$length = ord($text[1]) & 127
...mean, exactly?
Windows EXE files have some metadata like CompanyName, FileVersion, InternalName, ProductName, OriginalFileName, ProductVersion, etc.
How can I extract such metadata from using PHP?
I got curious about this, so I decided to write this function:
function getFileVersionInfo($filename,$encoding='UTF-8'){
$dat = file_get_contents($filename);
if($pos=strpos($dat,mb_convert_encoding('VS_VERSION_INFO','UTF-16LE'))){
$pos-= 6;
$six = unpack('v*',substr($dat,$pos,6));
$dat = substr($dat,$pos,$six[1]);
if($pos=strpos($dat,mb_convert_encoding('StringFileInfo','UTF-16LE'))){
$pos+= 54;
$res = [];
$six = unpack('v*',substr($dat,$pos,6));
while($six[2]){
$nul = strpos($dat,"\0\0\0",$pos+6)+1;
$key = mb_convert_encoding(substr($dat,$pos+6,$nul-$pos-6),$encoding,'UTF-16LE');
$val = mb_convert_encoding(substr($dat,ceil(($nul+2)/4)*4,$six[2]*2-2),$encoding,'UTF-16LE');
$res[$key] = $val;
$pos+= ceil($six[1]/4)*4;
$six = unpack('v*',substr($dat,$pos,6));
}
return $res;
}
}
}
It works with 32-bit and 64-bit exe. Usage example:
echo "<pre>".print_r(getFileVersionInfo('notepad.exe'),1)."</pre>";
echo "<pre>".print_r(getFileVersionInfo('php.exe'),1)."</pre>";
echo "<pre>".print_r(getFileVersionInfo('jre-7u9-windows-x64.exe'),1)."</pre>";
notepad.exe (32-bit):
Array
(
[CompanyName] => Microsoft Corporation
[FileDescription] => Notepad
[FileVersion] => 6.1.7600.16385 (win7_rtm.090713-1255)
[InternalName] => Notepad
[LegalCopyright] => © Microsoft Corporation. All rights reserved.
[OriginalFilename] => NOTEPAD.EXE
[ProductName] => Microsoft® Windows® Operating System
[ProductVersion] => 6.1.7600.16385
)
php.exe (32-bit):
Array
(
[Comments] => Thanks to Edin Kadribasic, Marcus Boerger, Johannes Schlueter, Moriyoshi Koizumi, Xinchen Hui
[CompanyName] => The PHP Group
[FileDescription] => CLI
[FileVersion] => 7.0.12
[InternalName] => CLI SAPI
[LegalCopyright] => Copyright © 1997-2016 The PHP Group
[LegalTrademarks] => PHP
[OriginalFilename] => php.exe
[ProductName] => PHP
[ProductVersion] => 7.0.12
[URL] => http://www.php.net
)
jre-7u9-windows-x64.exe (64-bit):
Array
(
[CompanyName] => Oracle Corporation
[FileDescription] => Java(TM) Platform SE binary
[FileVersion] => 7.0.90.5
[Full Version] => 1.7.0_09-b05
[InternalName] => Setup Launcher
[LegalCopyright] => Copyright © 2012
[OriginalFilename] => jinstall.exe
[ProductName] => Java(TM) Platform SE 7 U9
[ProductVersion] => 7.0.90.5
)
Something interesting about php.exe: the Comments and URL don't show up in the Details tab.
At least in my computer.
Enjoy.
Update 1: I forgot error checking. Now it returns null if the version info doesn't exist.
Update 2: Many thanks to #Abela for bringing an encoding issue to my attention.
I added an optional 2nd parameter that defaults to UTF-8 which should work for most purposes.
If you need single-byte-character output, use ISO-8859-1 instead, like this:
getFileVersionInfo('php.exe','ISO-8859-1');
This is what works for me. Works with .dll-s too.
function fsubstr($file_handle,$start,$lenght)
{
fseek($file_handle,$start);
$result = fread($file_handle,$lenght);
return $result;
}
function fGetFileVersion($FileName)
{
$handle = fopen($FileName, 'rb');
if(!$handle)
{
return FALSE;
}
$Header = fread($handle, 64);
if(substr($Header, 0, 2) != 'MZ')
{
return FALSE;
}
$PEOffset = unpack("V", substr($Header, 60, 4));
if($PEOffset[1] < 64)
{
return FALSE;
}
fseek($handle, $PEOffset[1], SEEK_SET);
$Header = fread($handle, 24);
if(substr($Header, 0, 2) != 'PE')
{
return FALSE;
}
$Machine = unpack("v", substr($Header, 4, 2));
if($Machine[1] != 332)
{
return FALSE;
}
$NoSections = unpack("v", substr($Header, 6, 2));
$OptHdrSize = unpack("v", substr($Header, 20, 2));
fseek($handle, $OptHdrSize[1], SEEK_CUR);
$ResFound = FALSE;
for ($x = 0; $x < $NoSections[1]; $x++)
{
$SecHdr = fread($handle, 40);
if (substr($SecHdr, 0, 5) == '.rsrc')
{
$ResFound = TRUE;
break;
}
}
if(!$ResFound)
{
return FALSE;
}
$InfoVirt = unpack("V", substr($SecHdr, 12, 4));
$InfoSize = unpack("V", substr($SecHdr, 16, 4));
$InfoOff = unpack("V", substr($SecHdr, 20, 4));
$offset = $InfoOff[1];
$NumDirs = unpack("v", fsubstr($handle, $offset + 14, 2));
$InfoFound = FALSE;
for ($x = 0; $x <$NumDirs[1]; $x++)
{
$Type = unpack("V", fsubstr($handle, $offset + ($x * 8) + 16, 4));
if($Type[1] == 16)
{
//FILEINFO resource
$InfoFound = TRUE;
$SubOff = unpack("V", fsubstr($handle, $offset + ($x * 8) + 20, 4));
break;
}
}
if (!$InfoFound)
{
return FALSE;
}
$SubOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", fsubstr($handle, $offset + $SubOff[1] + 20, 4)); //offset of first FILEINFO
$InfoOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 20, 4)); //offset to data
$DataOff = unpack("V", fsubstr($handle, $offset + $InfoOff[1], 4));
$DataSize = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 4, 4));
$CodePage = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 8, 4));
$DataOff[1] -= $InfoVirt[1];
$Version = unpack("v4", fsubstr($handle, $offset + $DataOff[1] + 48, 8));
return sprintf("%u.%u.%u.%u",$Version[2],$Version[1],$Version[4],$Version[3]);
}
I am working from this code and taking into account the comments to "fix" it for 32-bit, but it seems to still not work. I am pretty sure it has something to do with the TGA descriptor. This will have bits 0-3 as the alpha channel depth which will always be 8 for 32 bit, and the code doesn't account for that.
I tried to understand how to piece it together using this C code as a guide, but no luck.
It seems the once you take into account the pixel being of length of 4 (as per the patch in comments) his dwordize only accounts for 3 of the 4 bytes, the 4th byte being the alpha bits I think.
I tried changing the function from
function dwordize($str)
{
$a = ord($str[0]);
$b = ord($str[1]);
$c = ord($str[2]);
return $c*256*256 + $b*256 + $a;
}
to
function dwordize($str)
{
$a = ord($str[0]);
$b = ord($str[1]);
$c = ord($str[2]);
$d = ord($str[3]);
return $d*256*256*256 + $c*256*256 + $b*256 + $a;
}
which didn't work, and then tried
function dwordize($str)
{
$a = ord($str[0]);
$b = ord($str[1]);
$c = ord($str[2]);
$d = ord($str[3]);
return $c*256*256 + $b*256 + $a + $d*256*256*256;
}
All of which I am trying to go off the C code which goes from like RBGA to BGRA and then the indices are all weird. I really don't understand the C code enough to apply it to the PHP code.
I also found this site which may help, I am reading it over now and if I come up with anything I will update.
I have found your solution. There were missing adjustments that needed to be made in the RLE decode function to support the 32 bits pixel format: i.e: AARRGGBB
So first of all we need to bypass the color depth check of your library as suggested in the comments, so we change the check to this on line 99
if ($header['pixel_size'] != 24 && $header['pixel_size'] != 32) {
die('Unsupported TGA color depth');
}
Then we need to change some variables that are color depth dependent. Most of them will be used for data formatting so we need them to have the correct value regardless of the color depth.
So that would be:
line 104 : $bytes = $header['pixel_size'] / 8;
line 117 : $size = $header['width'] * $header['height'] * $bytes;
line 154 : $pixels = str_split($data, $bytes);
and remove the line 153: $num_bytes = $header['pixel_size']/8; We do not need it anymore.
Then we will need the rle_decode() function to know the pixel format size, so int the for loop when we call it we need to pass this parameter, so we need to change as well the following line:
line 141: $data = rle_decode($data, $size, $bytes);
Therefore the function prototypes changes into:
line 9: function rle_decode($data, $datalen, $pixel_size)
Now you can see on this function there are multiple 3 magic numbers (i.e: for ($j = 0; $j<3*$value; $j++) on line 29).
All of those must be replaced by the pixel size parameter. (3 for 24 bits and 4 for 32 bits)
Moreover sometimes it will write only 3 bytes instead of 4 when creating the pixels so we must adjust that as well.
So finally this gives us the following algorithm:
function rle_decode($data, $datalen, $pixel_size)
{
$len = strlen($data);
$out = '';
$i = 0;
$k = 0;
while ($i<$len)
{
dec_bits(ord($data[$i]), $type, $value);
if ($k >= $datalen)
{
break;
}
$i++;
if ($type == 0) //raw
{
for ($j=0; $j<$pixel_size*$value; $j++)
{
$out .= $data[$j+$i];
$k++;
}
$i += $value*$pixel_size;
}
else //rle
{
for ($j=0; $j<$value; $j++)
{
$out .= $data[$i] . $data[$i+1] . $data[$i+2];
if ($pixel_size == 4) $out .= $data[$i+3];
$k++;
}
$i += $pixel_size;
}
}
return $out;
}
That's it, you will now get your TGA images perfectly converted. I'm giving you the full tga.php code so you can directly paste it and test it by yourself:
<?php
// Author: de77
// Licence: MIT
// First-version: 9.02.2010
// Version: 24.08.2010
// http://de77.com
function rle_decode($data, $datalen, $pixel_size)
{
$len = strlen($data);
$out = '';
$i = 0;
$k = 0;
while ($i<$len)
{
dec_bits(ord($data[$i]), $type, $value);
if ($k >= $datalen)
{
break;
}
$i++;
if ($type == 0) //raw
{
for ($j=0; $j<$pixel_size*$value; $j++)
{
$out .= $data[$j+$i];
$k++;
}
$i += $value*$pixel_size;
}
else //rle
{
for ($j=0; $j<$value; $j++)
{
$out .= $data[$i] . $data[$i+1] . $data[$i+2];
if ($pixel_size == 4) $out .= $data[$i+3];
$k++;
}
$i += $pixel_size;
}
}
return $out;
}
function dec_bits($byte, &$type, &$value)
{
$type = ($byte & 0x80) >> 7;
$value = 1 + ($byte & 0x7F);
}
function getimagesizetga($filename)
{
$f = fopen($filename, 'rb');
$header = fread($f, 18);
$header = #unpack( "cimage_id_len/ccolor_map_type/cimage_type/vcolor_map_origin/vcolor_map_len/" .
"ccolor_map_entry_size/vx_origin/vy_origin/vwidth/vheight/" .
"cpixel_size/cdescriptor", $header);
fclose($f);
$types = array(0,1,2,3,9,10,11,32,33);
if (!in_array($header['image_type'], $types))
{
return array(0, 0, 0, 0, 0);
}
if ($header['pixel_size'] > 32)
{
return array(0, 0, 0, 0, 0);
}
return array($header['width'], $header['height'], 'tga', $header['pixel_size'], $header['image_type']);
}
function imagecreatefromtga($filename)
{
$f = fopen($filename, 'rb');
if (!$f)
{
return false;
}
$header = fread($f, 18);
$header = unpack( "cimage_id_len/ccolor_map_type/cimage_type/vcolor_map_origin/vcolor_map_len/" .
"ccolor_map_entry_size/vx_origin/vy_origin/vwidth/vheight/" .
"cpixel_size/cdescriptor", $header);
switch ($header['image_type'])
{
case 2: echo "Image is not compressed\n";//no palette, uncompressed
case 10: //no palette, rle
break;
default: die('Unsupported TGA format');
}
if ($header['pixel_size'] != 24 && $header['pixel_size'] != 32)
{
die('Unsupported TGA color depth');
}
$bytes = $header['pixel_size'] / 8;
if ($header['image_id_len'] > 0)
{
$header['image_id'] = fread($f, $header['image_id_len']);
}
else
{
$header['image_id'] = '';
}
$im = imagecreatetruecolor($header['width'], $header['height']);
$size = $header['width'] * $header['height'] * $bytes;
//-- check whether this is NEW TGA or not
$pos = ftell($f);
fseek($f, -26, SEEK_END);
$newtga = fread($f, 26);
if (substr($newtga, 8, 16) != 'TRUEVISION-XFILE')
{
$newtga = false;
}
fseek($f, 0, SEEK_END);
$datasize = ftell($f) - $pos;
if ($newtga)
{
$datasize -= 26;
}
fseek($f, $pos, SEEK_SET);
//-- end of check
$data = fread($f, $datasize);
if ($header['image_type'] == 10)
{
$data = rle_decode($data, $size, $bytes);
}
if (bit5($header['descriptor']) == 1)
{
$reverse = true;
}
else
{
$reverse = false;
}
$i = 0;
$pixels = str_split($data, $bytes);
//read pixels
if ($reverse)
{
for ($y=0; $y<$header['height']; $y++)
{
for ($x=0; $x<$header['width']; $x++)
{
imagesetpixel($im, $x, $y, dwordize($pixels[$i]));
$i++;
}
}
}
else
{
for ($y=$header['height']-1; $y>=0; $y--)
{
for ($x=0; $x<$header['width']; $x++)
{
imagesetpixel($im, $x, $y, dwordize($pixels[$i]));
$i++;
}
}
}
fclose($f);
return $im;
}
function dwordize($str)
{
$a = ord($str[0]);
$b = ord($str[1]);
$c = ord($str[2]);
return $c*256*256 + $b*256 + $a;
}
function bit5($x)
{
return ($x & 32) >> 5;
}
And here is the output PNG image directly created from the script:
And if you would prefer a direct download link rather than pasting the fixed code I give you here the full php file: https://www.sendspace.com/file/92uir9
At the end of the day, just change the tga.php file and everything will automagically work.
Based on the de77.com code, but with some additions:
<?php
// References:
// https://stackoverflow.com/questions/24709142/convert-32-bit-tga-to-png
// https://github.com/nothings/stb/blob/master/stb_image.h
// http://tfc.duke.free.fr/coding/tga_specs.pdf
// http://www.paulbourke.net/dataformats/tga/
//
// This class development started with the following code:
// http://de77.com/tga-imagetga-imagecreatefromtga
// Author: de77
// Licence: MIT
// First-version: 9.02.2010
// Version: 24.08.2010
// http://de77.com
//
// C.. oct 2022
// I put the code in a class,
// I added the color-mapped formats,
// I added the 15b & 16b pixel-size formats.
//
// The following is supported:
//
// Pixel-depths: 8b, 15b, 16b, 24b, 32b
//
// Image Type Description
// 0 No Image Data Included
// 1 Uncompressed, Color-mapped Image
// 2 Uncompressed, True-color Image
// 3 Uncompressed, Grayscale Image
// 9 Run-length encoded, Color-mapped Image
// 10 Run-length encoded, True-color Image
// 11 Run-length encoded, Grayscale Image
//
// NOTE: i commented out 16b-alpha code using: //!
// It does not work, and images come out all transparent.
class uje_tga_class {
private function rle_decode(&$data, $bytes) {
$out = '';
$i = 0;
$len = strlen($data);
while ($i<$len) {
$b = ord($data[$i]);
$type = ($b & 0x80);
$count = 1 + ($b & 0x7F);
$i++;
// raw or RLE
if ($type == 0) { //raw
$size = $bytes*$count;
$out .= substr($data, $i, $size);
$i += $size;
} else { //rle
$s = substr($data, $i, $bytes);
$out .= str_repeat($s, $count);
$i += $bytes;
}
}
return $out;
}
private function unmapcolors(&$data, $npixels, $colormapentrybytes, &$palette, $pixelbytes) {
$out = '';
for ($i=0, $i2=0; $i<$npixels; $i++, $i2+=$colormapentrybytes) {
$idx = ord($data[$i2]); // $colormapentrybytes == 1 or 2
if ($colormapentrybytes == 2) $idx += (ord($data[$i2+1]) << 8);
$idx *= $pixelbytes;
$out .= substr($palette, $idx, $pixelbytes);
}
return $out;
}
public function getimagesizetga($filename) {
$f = fopen($filename, 'rb');
$header = fread($f, 18);
$header = #unpack("cimage_id_len/ccolor_map_type/cimage_type/vcolor_map_origin/vcolor_map_len/" .
"ccolor_map_entry_size/vx_origin/vy_origin/vwidth/vheight/" .
"cpixel_size/cdescriptor", $header);
fclose($f);
$types = array(0,1,2,3,9,10,11,32,33);
if (!in_array($header['image_type'], $types)) return array(0, 0, 0, 0, 0);
if ($header['pixel_size'] > 32) return array(0, 0, 0, 0, 0);
return array($header['width'], $header['height'], 'tga', $header['pixel_size'], $header['image_type']);
}
public function imagecreatefromtga($filename) {
// open the TGA file for binary reading
$f = fopen($filename, 'rb');
if (!$f) return false;
// read the file
try {
// read the TGA header
$header = fread($f, 18);
$header = unpack("cimage_id_len/ccolor_map_type/cimage_type/" .
"vcolor_map_origin/vcolor_map_len/ccolor_map_entry_size/" .
"vx_origin/vy_origin/vwidth/vheight/" .
"cpixel_size/cdescriptor", $header);
// check for supported tga formats
switch ($header['image_type']) {
case 1: // color-mapped uncompressed
case 2: // truecolor uncompressed
case 3: // grayscale uncompressed
case 9: // color-mapped run-length encoded
case 10: // truecolor run-length encoded
case 11: // grayscale run-length encoded
break;
default:
throw new RuntimeException('Unsupported format: '. $header['image_type']);
}
$iscolormapped = ($header['image_type'] == 9 || $header['image_type'] == 1);
$isrle = ($header['image_type'] == 9 || $header['image_type'] == 10 || $header['image_type'] == 11);
// check for supported pixel sizes. ($header['pixel_size'])
// But if this is a colormapped image, that "pixel_size" is in fact the width of each entry in the colormap.
// For a colormapped image, the pixelsize is stored in $header['color_map_entry_size'].
if ($iscolormapped) {
if ($header['color_map_type'] == 0) {
throw new RuntimeException('Missing colormap');
}
$pixelbits = $header['color_map_entry_size'];
// the entries in the colormap can be 8 or 16 bit wide
$colormapentrybytes = $header['pixel_size']; // this is really the number of bits..
if (($colormapentrybytes != 8) && ($colormapentrybytes != 16)) {
throw new RuntimeException('Unsupported colormap entry bits: '. $colormapentrybytes);
}
$colormapentrybytes /= 8; // ..now it's bytes
} else {
$pixelbits = $header['pixel_size'];
}
switch ($pixelbits) {
case 8: // grayscale
$pixelbytes = 1;
break;
case 15: // truecolor 5b blue + 5b green + 5b red + 1b dummy
case 16: // truecolor 5b blue + 5b green + 5b red + 1b alpha
$pixelbytes = 2;
break;
case 24: // truecolor
$pixelbytes = 3;
break;
case 32: // truecolor
$pixelbytes = 4;
break;
default:
throw new RuntimeException('Unsupported pixel bits: '. $pixelbits);
}
// skip the image_id
$header['image_id'] = ($header['image_id_len'] > 0)? fread($f, $header['image_id_len']) : '';
// check whether this is a TGA v2.0
$tgav2 = true;
$pos = ftell($f); // store the current filepointer
fseek($f, -26, SEEK_END); // (possibly) read the file footer
$footer = fread($f, 26);
if (substr($footer, 8, 16) != 'TRUEVISION-XFILE') $tgav2 = false;
fseek($f, 0, SEEK_END);
$datasize = ftell($f) - $pos;
if ($tgav2) $datasize -= 26; // pixeldata does not include any footer
fseek($f, $pos, SEEK_SET); // restore filepointer
// if color-mapped then read the palette.
// The palette starts at the file-location where the image-data starts for the other formats.
// So first read the palette, and then correct the final datasize.
if ($iscolormapped) {
$palettesize = $header['color_map_len'] * $pixelbytes;
$pos = ftell($f) + $header['color_map_origin'];
fseek($f, $pos, SEEK_SET); // set filepointer to palette
$palette = fread($f, $palettesize);
$datasize -= $palettesize;
}
// Read the image data.
// If this is a colormapped image then this is not the pixeldata, but the indexes into the colormap.
$data = fread($f, $datasize);
} catch (Exception $e) {
//echo 'Exception: ', $e->getMessage(), "\n";
return false;
} finally {
fclose($f);
}
// get the pixeldata
if ($iscolormapped) {
$npixels = $header['width'] * $header['height'];
// colormapped images have the color-indexes encoded (pixeldata must be looked-up after RL decoding)
if ($isrle) $data = $this->rle_decode($data, $colormapentrybytes);
$pixeldata = $this->unmapcolors($data, $npixels, $colormapentrybytes, $palette, $pixelbytes);
} else
if ($isrle) { // possibly Run Length decode
$pixeldata = $this->rle_decode($data, $pixelbytes);
} else // uncompressed
$pixeldata = $data;
// create the image
$im = imagecreatetruecolor($header['width'], $header['height']);
// if the image has alpha data, then prepare for it
imagealphablending($im, false); // no blending. Just copy the pixel
//! if (!$iscolormapped && ($header['pixel_size'] == 32 || $header['pixel_size'] == 16)) {
if (!$iscolormapped && ($header['pixel_size'] == 32)) {
imagesavealpha($im, true); // be sure to save the alpha data
} else {
imagesavealpha($im, false);
}
// read pixel-ordering
$toptobottom = (($header['descriptor'] & 32) != 0);
$righttoleft = (($header['descriptor'] & 16) != 0);
// process the pixels
$i = 0;
for ($y=0; $y<$header['height']; $y++)
for ($x=0; $x<$header['width']; $x++) {
switch($pixelbytes) {
case 1:
$r = $g = $b = ord($pixeldata[$i]);
$col = imagecolorallocate($im, $r,$g,$b);
break;
case 2:
$r = (ord($pixeldata[$i+1]) & 0x7C) >> 2;
$g = ((ord($pixeldata[$i]) & 0xE0) >> 5) + ((ord($pixeldata[$i+1]) & 0x03) << 3);
$b = ord($pixeldata[$i]) & 0x1F;
$r <<= 3;
$g <<= 3;
$b <<= 3;
//! if ($header['pixel_size'] == 16) {
//! $a = ((ord($pixeldata[$i+1]) & 0x80) == 0)? 127 : 0; // 1b alpha means? transparent : opaque
//! $col = imagecolorallocatealpha($im, $r,$g,$b,$a);
//! } else {
$col = imagecolorallocate($im, $r,$g,$b);
//! }
break;
case 3:
$r = ord($pixeldata[$i+2]);
$g = ord($pixeldata[$i+1]);
$b = ord($pixeldata[$i]);
$col = imagecolorallocate($im, $r,$g,$b);
break;
case 4:
$r = ord($pixeldata[$i+2]);
$g = ord($pixeldata[$i+1]);
$b = ord($pixeldata[$i]);
$a = 127 - (ord($pixeldata[$i+3]) >> 1); // 128 alpha values with png transulency (where 127 = transparent, 0 = opaque)
$col = imagecolorallocatealpha($im, $r,$g,$b,$a);
break;
}
// set the pixel in the image
$xp = ($righttoleft)? $header['width'] - 1 - $x : $x;
$yp = ($toptobottom)? $y : $header['height'] - 1 - $y;
imagesetpixel($im, $xp, $yp, $col);
// next pixel in the pixeldata
$i += $pixelbytes;
}
return $im;
}
public function imagetga($im, $filename) {
list($width, $height) = array(imagesx($im), imagesy($im));
$tga = "\0\0\2\0\0\0\0\0\0\0\0\0" . pack('vv', $width, $height) . ' ';
for ($y=0; $y<$height; $y++)
for ($x=0; $x<$width; $x++) {
$rgb = imagecolorat($im, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$a = ($rgb >> 24) & 0x7F;
$tga .= chr($b).chr($g).chr($r).chr((127-$a)*2);
}
file_put_contents($filename, $tga);
}
public function tga2png($tga_filename, $png_filename) {
$im = $this->imagecreatefromtga($tga_filename);
if (!$im) return false;
//header('Content-Disposition: Attachment;filename=image.png');
//header("Content-type: image/png");
imagepng($im, $png_filename);
imagedestroy($im);
return true;
}
}
?>
I have an problem with PHP function imagettftext. I have code for generating card images from database with text informations. And with some cards I have problem - words are written over each other (like here).
My code looks like this (font size changes depending on the length of the text)
$length = strlen($cardInfo->description);
if ($length < 15) {
$divide = 15;
$fontSize = 16;
$lineHeight = 25;
$startPos = 220;
} else if ($length < 70) {
$divide = 25;
$fontSize = 12;
$lineHeight = 18;
$startPos = 210;
} else if ($length < 110) {
$divide = 28;
$fontSize = 10;
$lineHeight = 14;
$startPos = 210;
} else {
$divide = 38;
$fontSize = 8;
$lineHeight = 13;
$startPos = 210;
}
$description = wordwrap($cardInfo->description, $divide, ">>>");
$lines = explode(">>>", $description);
$count = 0;
foreach ($lines as $line) {
$position = $count * $lineHeight;
$count++;
imagettftext($image, $fontSize, 0, 28, ($startPos + $position), $black, $font, $line);
}
and the text in the database looks like this:
Oblehací stroj
Imunita vůči střelám /Tato jednotka je imunní vůči střeleckým zraněním/
Other problem is with the line wrapping: here. I don't know why the word "jídlo" is on the next line.
Thank you for any answers!
Long time ago, I have written a quite complex class to archive a similar task.
I don't have this code anymore, but the steps are fairly simple.
First: Don't rely on the calculations, php does on rare fonts.
Php's wordwrap-function is senseless here, because you deal with a charset-width (e.g. tracking) unknown to php. Wordwrap assumes, that all characters have the same char-width.
So, you have to build your own wordwrap-function using imagettfbbox. Then, you'll have to determine the size of the lowercase "x"-letter and the uppercase "X"-letter. These letters are the norm to calculate your own line-height/line-spacing. I also recommend you to manually separate words, since PHP does not always recognize the white-space-width correctly.
Hope this could help you...
This works for me. You need arial.ttf and http://dark-project.cz/CardDatabase/cards/lehky_katapult.png.
class Test_Canvas {
protected $res=null;
public function __construct($width, $height) {
$this->res = imagecreatetruecolor($width, $height);
imagealphablending($this->res, true);
imagesavealpha($this->res, true);
imagefill($this->res, 0, 0, imagecolorallocatealpha($this->res, 0, 0, 0, 127));
}
public function copyTo(Test_Canvas $canvas, $x, $y) {
imagecopyresampled($canvas->res, $this->res, $x, $y, 0, 0, $this->getWidth(), $this->getHeight(), $this->getWidth(), $this->getHeight());
}
public function getWidth() {
return imagesx($this->res);
}
public function getHeight() {
return imagesy($this->res);
}
public function saveAsPNG() {
imagepng($this->res);
}
}
class Test_Canvas_Image_PNG extends Test_Canvas {
public function __construct($filename) {
$res = imagecreatefrompng($filename);
$w = imagesx($res);
$h = imagesy($res);
parent::__construct($w, $h);
imagecopymerge($this->res, $res, 0, 0, 0, 0, $w, $h, 100);
}
}
class Test_Canvas_Textarea extends Test_Canvas {
private $text;
private $fontsize;
private $fontfile;
public function __construct($width, $height, $text, $fontsize, $fontfile) {
parent::__construct($width, $height);
$this->text = $text;
$this->fontsize = $fontsize;
$this->fontfile = $fontfile;
$this->removeDuplicateWhitespace();
$this->formatText();
$this->applyText();
}
private function removeDuplicateWhitespace() {
$this->text = preg_replace('/[ \t]+/', ' ', $this->text);
}
private function formatText() {
$lines = explode("\n", $this->text);
$res = array();
foreach ($lines as $line) {
$res[] = $this->insertAdditionalLinebreaks($line);
}
$this->text = join("\n", $res);
}
private function insertAdditionalLinebreaks($line) {
$words = $this->splitWords($line);
$res = array();
$line = "";
while(count($words)) {
$word = array_shift($words);
$testLine = "{$line} {$word}";
$width = $this->getTextWidth($testLine);
if($width > $this->getWidth()) {
$res[] = $line;
$line = $word;
} elseif(!count($words)) {
$res[] = $testLine;
} else {
$line = $testLine;
}
}
return join("\n", $res);
}
private function getTextWidth($text) {
$boundaries = imagettfbbox($this->fontsize, 0, $this->fontfile, $text);
$x1 = min($boundaries[0], $boundaries[6]);
$x2 = max($boundaries[2], $boundaries[4]);
return $x2 - $x1;
}
private function splitWords($text) {
return explode(' ', $text);
}
private function applyText() {
$lines = explode("\n", $this->text);
foreach($lines as $lineNo => $line) {
imagettftext($this->res, $this->fontsize, 0, 0, ($lineNo + 1) * ($this->fontsize + 5), imagecolorallocate($this->res, 0, 0, 0), $this->fontfile, $line);
}
}
}
$rootPath = dirname(__FILE__).'/';
$imageFilename = "{$rootPath}test.png";
$description = "Oblehací stroj\nImunita vuci strelám /Tato jednotka je imunní vuci streleckým zranením/ ";
$description .= $description;
$description .= $description;
header('Content-Type: image/png');
$canvas = new Test_Canvas_Image_PNG($imageFilename);
$text = new Test_Canvas_Textarea(179, 92, $description, 9, 'arial.ttf');
$text->copyTo($canvas, 25, 193);
$canvas->saveAsPNG();
You shouldn't use such estimates for the box you'll need.
The function imagettfbbox() can give you a definite answer to the box you'll need to display the text.
http://nl3.php.net/manual/en/function.imagettfbbox.php
Hope that helps.
I'm writing some app based on websockets (RFC 6455). Unfortunetly it looks like the client (testing on Chrome 18) doesn't receive data, but the server says it is sending...
Chrome doesn't say anything
Here are main server methods:
private function decode($payload) {
$length = ord($payload[1]) & 127;
if ($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
} elseif ($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
} else {
$masks = substr($payload, 2, 4);
$data = substr($payload, 6);
}
$text = '';
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i % 4];
}
$text = base64_decode($text);
return $text;
}
private function encode($text) {
$text = base64_encode($text);
// 0x1 text frame (FIN + opcode)
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);
if ($length <= 125)
$header = pack('CC', $b1, $length);
elseif ($length > 125 && $length < 65536)
$header = pack('CCS', $b1, 126, $length);
else
$header = pack('CCN', $b1, 127, $length);
return $header . $text;
}
protected function process($user, $msg) {
echo '<< '.$msg.N;
if (empty($msg)) {
$this->send($user->socket, $msg);
return;
}
}
protected function send($client, $msg) {
$msg = $this->encode($msg);
echo '>> '.$msg.N;
socket_write($client, $msg, strlen($msg));
}
If you're sending a test message >125 bytes but <65536, your problem might be caused by a faulty format string to pack. I think this one should be 'CCn' (your current code writes the 2 bytes of the length in the wrong order).
If that doesn't help, you could try some client-side logging:
Does the onopen callback run to prove that the initial handshake completed successfully?
Do the onerror or onclose callbacks run, either after connection or after your server sends its message?