DBPF Source Code

From SC4D Encyclopaedia
Revision as of 14:54, 8 September 2012 by sc4e>Whatevermind (Created page)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search


This page contains a sample PHP class to read DBPF files and extract information about them. See also DBPF Compression for more information about compressed parts of DBPF files.

Use this code at your own risk. Any comments please use the Talk:DBPF Source Code page.

Source Code

<?

class DBPFfile
{

        var $majorVersion;
        var $minorVersion;

        var $reserved;

        var $dateCreated;
        var $dateModified;

        var $indexMajorVersion;
        var $indexMinorVersion;

        var $indexCount;
        var $indexOffset;
        var $indexSize;

        var $holesCount;
        var $holesOffset;
        var $holesSize;

        var $reserved2;

        var $indexData;


        // ** Internally used I/O functions

        // Reads a 4 byte unsigned integer
        /*
                Used internally by the class to read a C/C++
                "unsigned long" (a 4 byte unsigned integer)
                from an open file

                $fh - the file handle from which to read
                returns - returns the value read; has no error return
        */
        function read_UL4($fh)
        {
                $d = fread($fh, 4);
                $a = unpack("Vn", $d);
                return $a["n"];
        }

        // Reads a 2 byte unsigned integer
        /*
                Used internally by the class to read a C/C++
                "unsigned short" (a 2 byte unsigned integer)
                from an open file

                $fh - the file handle from which to read
                returns - returns the value read; has no error return
        */
        function read_UL2($fh)
        {
                $d = fread($fh, 2);
                $a = unpack("vn", $d);
                return $a["n"];
        }

        // Reads a 1 byte unsigned integer
        /*
                Used internally by the class to read a C/C++
                "unsigned char" (a 1 byte unsigned integer)
                from an open file

                $fh - the file handle from which to read
                returns - returns the value read; has no error return
        */
        function read_UL1($fh)
        {
                $d = fread($fh, 1);
                $a = unpack("Cn", $d);
                return $a["n"];
        }

        // Reads a Unicode string of specified length
        /*
                Used internally by the class to read a C/C++
                array of "wchar_t" (a 2 byte character)
                from an open file

                $fh - the file handle from which to read
                $len - the number of characters to read
                returns - returns the value read; has no error return
        */
        function read_unistr($fh, $len)
        {
                $s = "";
                for($i = 0; $i < $len; ++$i)
                {
                        $c = fread($fh, 1);
                        if(ord($c) != 0) $s .= $c;
                        fseek($fh, 1, SEEK_CUR);
                }
                return $s;
        }

        function read_nullstring($fh)
        {
                $s = "";
                $str = fread($fh, 1);
                while (ord($str) != 0)
                {
                        $s .= $str;
                        $str = fread($fh, 1);
                }

                return $s;

        }

        // Reads a string length and then the string
        /*
                Used internally by the class to read a 4 byte unsigned integer
                and then read a string that many characters in length

                $fh - the file handle from which to read
                returns - returns the string read (not the length read); has no error return
        */
        function read_lenstr($fh)
        {
                $str_length = $this->read_UL4($fh);
                $s = fread($fh, $str_length);
                return $s;
        }

        // Reads a string length and then the Unicode string
        /*
                Used internally by the class to read a 4 byte unsigned integer
                and then read a Unicode string that many characters in length

                $fh - the file handle from which to read
                returns - returns the Unicode string read (not the length read); has no error return
        */
        function read_lenwstr($fh)
        {
                $str_length = $this->read_UL4($fh);
                $s = $this->read_unistr($fh, $str_length);
                return $s;
        }

        function strhex($string)
        {
                $hex="";
                for ($i=0;$i<strlen($string);$i++) $hex.=(strlen(dechex(ord($string[$i])))<2)? "0".dechex(ord($string[$i])): dechex(ord($string[$i]));
                return $hex;
        }

        function hexstr($hex)
        {
                $string="";
                for ($i=0;$i<strlen($hex)-1;$i+=2) $string.=chr(hexdec($hex[$i].$hex[$i+1]));
                return $string;
        }

        // Reads a string and reverses it, taking into account hex groupings:
        /*
                for example, 2397586c becomes 6c589723
        */
        function revhex($string)
        {
                for($i=0;$i<strlen($string)-1;$i+=2)
                {
                        $revstring = $string[$i].$string[$i+1].$revstring;
                }
                return $revstring;
        }

        // Decompression function used on string
        /*
                PHP DBPF decompression by Delphy
                Thanks to dmchess (http://hullabaloo.simshost.com/forum/viewtopic.php?t=6578&postdays=0&postorder=asc)
                for the Perl code that was used for this.

                $handle - file handle for reading
                $len - length of compressed string

        */
        function decompress($handle, $len) {

                $buf = '';
                $answer = "";
                $answerlen = 0;
                $numplain = "";
                $numcopy = "";
                $offset = "";

                for (;$len>0;) {

                        $cc = $this->read_UL1($handle);
                        $len -= 1;

                //      echo $cc."<br />\n";

                //      printf("      Control char is %02x, len remaining is %08x. \n",$cc,$len);
                        if ($cc >= 252): // 0xFC
                                $numplain = $cc & 0x03;
                                if ($numplain > $len) { $numplain = $len; }
                                //$numplain = $len if ($numplain > $len);
                                $numcopy = 0;
                                $offset = 0;
                        elseif ($cc >= 224): // 0xE0
                                $numplain = ($cc - 0xdf) << 2;
                                $numcopy = 0;
                                $offset = 0;
                        elseif ($cc >= 192): // 0xC0
                                $len -= 3;

                                /*
                                $buf = fread($handle, 1);
                                $byte1 = unpack("C", $buf);
                                $buf = fread($handle, 1);
                                $byte2 = unpack("C", $buf);
                                $buf = fread($handle, 1);
                                $byte3 = unpack("C", $buf);
                                */

                                $byte1 = $this->read_UL1($handle);
                                $byte2 = $this->read_UL1($handle);
                                $byte3 = $this->read_UL1($handle);

                                $numplain = $cc & 0x03;
                                $numcopy = (($cc & 0x0c) <<6) + 5 + $byte3;
                                $offset = (($cc & 0x10) << 12 ) + ($byte1 << 8) + $byte2;
                        elseif ($cc >= 128): // 0x80
                                $len -= 2;

                                /*
                                $buf = fread($handle, 1);
                                $byte1 = unpack("C", $buf);
                                $buf = fread($handle, 1);
                                $byte2 = unpack("C", $buf);
                                */

                                $byte1 = $this->read_UL1($handle);
                                $byte2 = $this->read_UL1($handle);

                                $numplain = ($byte1 & 0xc0) >> 6;
                                $numcopy = ($cc & 0x3f) + 4;
                                $offset = (($byte1 & 0x3f) << 8) + $byte2;
                        else:
                                $len -= 1;

                                $byte1 = $this->read_UL1($handle);
                                //$buf = fread($handle, 1);
                                //$byte1 = unpack("C", $buf);

                                $numplain = ($cc & 0x03);
                                $numcopy = (($cc & 0x1c) >> 2) + 3;
                                $offset = (($cc & 0x60) << 3) + $byte1;
                        endif;

                        #    printf "      plain, copy, offset: $numplain, $numcopy, $offset \n";
//                      echo "      plain, copy, offset: $numplain, $numcopy, $offset \n";
                        $len -= $numplain;

//                      echo $numplain." -- ".$len."<br />\n";

                        if ($numplain > 0) {
                                $buf = fread($handle, $numplain);
                                $answer = $answer.$buf;
                        }

                        $fromoffset = strlen($answer) - ($offset + 1);  # 0 == last char
                        for ($i=0;$i<$numcopy;$i++) {
                                $answer = $answer.substr($answer,$fromoffset+$i,1);
                        }

                        $answerlen += $numplain;
                        $answerlen += $numcopy;
                }
                //printf "      Answer length is %08x (%08x). \n",$answerlen,length($answer);

                return $answer;
        }

        // Loads a DBPF file
        /*
                $fileName - the name of a file to open
                returns - true on success, a string value on error (use $ret === true to test for success or failure)
        */
        function loadFile($handle, $offset = 0)
        {
//                $this->m_fileName = $fileName;

//                $handle = fopen($fileName, "rwb");
                if($handle)
                {

                       echo "Reading from offset: ".$offset."<br />";

                        $start = $offset;
			if ($offset > 0) {
                        fseek($handle, $offset);
			}

                        $test = fread($handle, 4);  // Should be DBPF

			if ($test != "DBPF") { echo $test." - This is not a DBPF file!"; return; }

                        $this->majorVersion = $this->read_UL4($handle);
                        $this->minorVersion = $this->read_UL4($handle);

                        $this->reserved = fread($handle, 12);

                        $this->dateCreated = $this->read_UL4($handle);
                        $this->dateModified = $this->read_UL4($handle);

                        $this->indexMajorVersion = $this->read_UL4($handle);
                        $this->indexCount = $this->read_UL4($handle);
                        $this->indexOffset = $this->read_UL4($handle);
                        $this->indexSize = $this->read_UL4($handle);

                        $this->holesCount = $this->read_UL4($handle);
                        $this->holesOffset = $this->read_UL4($handle);
                        $this->holesSize = $this->read_UL4($handle);

                        $this->indexMinorVersion = $this->read_UL4($handle) - 1;

                        $this->reserved2 = fread($handle, 32);

                        $headerEnd = ftell($handle);

                        // Seek to index
                        fseek($handle, $offset + $this->indexOffset);
		//	echo ftell($handle)."\n";

                        for ($i=0;$i < $this->indexCount;$i++) {
                                $indexData[$i]['typeID'] = $this->revhex($this->strhex(fread($handle, 4)));
                                $indexData[$i]['groupID'] = $this->revhex($this->strhex(fread($handle, 4)));
                                $indexData[$i]['instanceID'] = $this->revhex($this->strhex(fread($handle, 4)));

				if (($this->indexMajorVersion == "7") && ($this->indexMinorVersion == "1")) {
	                                $indexData[$i]['instanceID2'] = $this->revhex($this->strhex(fread($handle, 4)));
				}
                                $indexData[$i]['offset'] = $this->read_UL4($handle);
                                $indexData[$i]['filesize'] = $this->read_UL4($handle);
                                $indexData[$i]['compressed'] = false;
                                $indexData[$i]['truesize'] = 0;
                        }

			//echo print_r($indexData, true)."\n";

                        // First check for a DIR resource
                        foreach($indexData as $value) {
                                if ($value['typeID'] == 'e86b1eef') {
                                        //echo "Getting DIR resource of size ".$value['filesize']."... <br />";
                                        fseek($handle, $offset + $value['offset']);

                                        // Each record in a Sims 2 file is 20 bytes long, so to get the total number of records we
                                        // divide $value['filsize'] by 20.
                                        if (($this->indexMajorVersion == "7") && ($this->indexMinorVersion == "1")) {
                                                $numRecords = ($value['filesize'] / 20);
                                        } else {
                                                $numRecords = ($value['filesize'] / 16);
                                        }

                                        for ($i=0;$i < $numRecords; $i++) {
                                                $typeID = $this->revhex($this->strhex(fread($handle, 4)));
                                                $groupID = $this->revhex($this->strhex(fread($handle, 4)));
                                                $instanceID = $this->revhex($this->strhex(fread($handle, 4)));
						if (($this->indexMajorVersion == "7") && ($this->indexMinorVersion == "1")) {
                                                	$instanceID2 = $this->revhex($this->strhex(fread($handle, 4)));
						}
                                                $filesize_nc = $this->read_UL4($handle);

                                                for ($j=0; $j < $this->indexCount; $j++) {
                                                        if ($indexData[$j]['typeID'] == $typeID) {
                                                        if ($indexData[$j]['groupID'] == $groupID) {
                                                        if ($indexData[$j]['instanceID'] == $instanceID) {
							if (($this->indexMajorVersion == "7") && ($this->indexMinorVersion == "1")) {
                                                        	if ($indexData[$j]['instanceID2'] == $instanceID2) {
                                                                	$indexData[$j]['compressed'] = true;
	                                                                $indexData[$j]['truesize'] = $filesize_nc;
        	                                                } 
							} else {
                        	                                $indexData[$j]['compressed'] = true;
                                                                $indexData[$j]['truesize'] = $filesize_nc;
							}
							} 
							} 
							}
                                                }

                                        }

                                }
                        }

                        foreach($indexData as $value) {

                                /* Catalog Description - CTSS */
                                if ($value['typeID'] == '43545353') {
                                        //echo "Grabbing CTSS file at offset ".$value['offset']." (".($offset + $value['offset']).")...<br />";
                                        fseek($handle, $offset + $value['offset']);

                                        fread($handle, 64);

                                        fread($handle, 2); // FormatCode
                                        $numStrings = $this->read_UL2($handle);
                                        //echo "NumStrings: ".$numStrings."<br />";

                                        $numStrings = 2;
                                        for ($j = 0; $j < $numStrings; $j++)
                                        {

                                                $stringPairs = $this->read_UL1($handle);
                                                //echo "NumStringPairs: ".$stringPairs."<br />";
                                                //for ($i = 0; $i < $stringPairs; $i++)
                                                //{
                                                        $ret .= $this->read_nullstring ($handle);
                                                        if ($j == 0) { $ret .= "<br />"; }
                                                        echo "...".$ret." - ";
                                                        $ret2 = $this->read_nullstring($handle);
                                                        //echo $ret2."<br />";
                                                //}
                                        }

                                }

                                /* Object XML */
                                if ($value['typeID'] == 'cca8e925') {
                                        if ($value['compressed'] == true) {
                                                fseek($handle, $offset + $value['offset']);
                                                $dword = $this->read_UL4($handle);
                                                //$dword = fread($handle, 4);
                                                $data = fread($handle, 5);

                                                //echo $dword."<br /> \n ";
                                                //echo $data."<br /> \n ";
                                                //echo "decompressing file... <br />";
                                                $xmldata =  $this->decompress($handle, $dword - 9);

                                                $objXML = new xml2Array();
                                                $arrOutput2 = $objXML->parse($xmldata);

                                                foreach ($arrOutput2[CGZPROPERTYSETSTRING][ANYSTRING] as $value) {
                                                        if ($value[KEY] == 'description') {
                                                                $ret = $value[DATA];
                                                                echo "-- ".$value[DATA]."<br />";
                                                        }
                                                }

                                        }
                                }


                        }

                        if ($ret == '') {
                        foreach ($indexData as $value) {

                                /* String Text Lists - STR# */
                                if ($value['typeID'] == '53545223') {
                                        echo "Grabbing STR# file at offset ".$value['offset']." (".($offset + $value['offset']).")...\n";
                                        fseek($handle, $offset + $value['offset']);

                                        if ($value['compressed'] == true) {
                                                fseek($handle, $offset + $value['offset']);
                                                $dword = $this->read_UL4($handle);
                                                //$dword = fread($handle, 4);
                                                $data = fread($handle, 5);

                                                //echo $dword."<br /> \n ";
                                                //echo $data."<br /> \n ";
                                                //echo "decompressing file... <br />";
                                                $data =  $this->decompress($handle, $dword - 9);

                                                // $data now contains the decompressed data so we have to get the string pairs from it

                                                //echo strlen($data)."\n";

						//if (strlen($data) > 66) {
                                                  // First, chop off the filename of 64 bytes
                                                  $data = substr($data, 64);
						//}

                                                // Next, the FormatCode (2 bytes)
                                                $data = substr($data, 2);

						//echo $data."\n";

                                                $numStrings = unpack("vn", substr($data, 0, 2));
                                                //echo "NumStrings: ".$numStrings."\n";

                                                $numStrings = 2;
                                                $soffset = 2;
                                                for ($j = 0; $j < $numStrings; $j++)
                                                {

							if ($soffset < strlen($data)) {

                                                        $languageCode = unpack("Cn", substr($data, $soffset, 1));
                                                        $soffset++;

                                                        // Grab string pair
                                                        for ($l=0; $l < 2; $l++) {
                                                        $k = 0;
                                                        for (;$k < 1;) {
                                                                $tempstring = substr($data, $soffset, 1);
                                                                if (ord($tempstring) != 0) {
                                                                        $ret .= $tempstring;
                                                                } else {
                                                                        $k = 1;
                                                                }
                                                                $soffset++;
                                                        }

                                                        }

                                                        //$ret .= $this->read_nullstring ($handle);
                                                        if ($j == 0) { $ret .= "<br />"; }
                                                        echo "...".$ret." - ";
                                                        //$ret2 = $this->read_nullstring($handle);
                                                        //echo $ret2."<br />";
							}

                                                }
                                        } else {
                                                fseek($handle, $offset + $value['offset']);

                                                fread($handle, 64);

                                                fread($handle, 2); // FormatCode
                                                $numStrings = $this->read_UL2($handle);
                                                //echo "NumStrings: ".$numStrings."<br />";

                                                $numStrings = 2;
                                                for ($j = 0; $j < $numStrings; $j++)
                                                {

                                                        $languageCode = $this->read_UL1($handle);
                                                        $ret .= $this->read_nullstring ($handle);
                                                        if ($j == 0) { $ret .= "<br />"; }
                                                        echo "...".$ret." - ";
                                                        $ret2 = $this->read_nullstring($handle);
                                                        //echo $ret2."<br />";
                                                }

                                        }

                                }

                        } }


                        return $ret;
                }
                else
                {
                        return "Could not open file '" . $fileName . "'";
                }

        }


}

?>