DBPF/Source Code
From SimsWiki
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.
<? 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 . "'"; } } } ?>