DBPF/Source Code

From SimsWiki
< DBPF
Revision as of 05:09, 31 December 2006 by Delphy (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Overview

This page contains a sample PHP class to read DBPF .package files and extract information about them.

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

Source

<?

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 charatcers 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
        /*
                ie 2397586c becomes 6c589723
        */
        function revhex($string)
        {
                for($i=0;$i<strlen($string)-1;$i+=2)
                {
                        $revstring = $string[$i].$string[$i+1].$revstring;
                }
                return $revstring;
        }

        // Decompresses 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 I 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 . "'";
                }

        }


}

?>

See Also

Personal tools
Namespaces

Variants
Actions
Navigation
game select
Toolbox