Difference between pages "DBPF Source Code" and "FSH Format"

From SC4D Encyclopaedia
(Difference between pages)
Jump to navigation Jump to search
m (1 revision imported)
 
sc4e>Null45
(Minor fix to the DXT1 alpha description)
 
Line 1: Line 1:
{{MTS2}}
+
EA PC SHAPE FORMAT - By Darkmatter and Karybdis
  
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==
+
== FSH SHPI Header ==
<pre>
 
<?
 
  
class DBPFfile
+
SHPI (4 bytes)
{
 
  
        var $majorVersion;
+
INT32 - File Size
        var $minorVersion;
 
  
        var $reserved;
+
INT32 - Number of Entries
  
        var $dateCreated;
+
Directory ID (4 bytes)
        var $dateModified;
 
  
        var $indexMajorVersion;
 
        var $indexMinorVersion;
 
  
        var $indexCount;
+
'''Directory ID legal entries''':
        var $indexOffset;
 
        var $indexSize;
 
  
        var $holesCount;
+
G354 - Building Textures
        var $holesOffset;
+
        var $holesSize;
+
G264 - Network Textures, Sim Textures, Sim heads, Sim animations, Trees, props, Base textures, Misc colours
  
        var $reserved2;
+
G266 - 3d Animation textures (e.g. the green rotating diamond in loteditor.dat)
  
        var $indexData;
+
G290 - Dispatch marker textures
  
 +
G315 - Small Sim texture, Network Transport Model Textures (trains etc)
  
        // ** Internally used I/O functions
+
GIMX - UI Editor textures
  
        // Reads a 4 byte unsigned integer
+
G344 - BAT gen texture maps
        /*
 
                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
+
== FSH Directory ==
        /*
 
                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
+
Entry Name (4 bytes)
                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
+
INT32 - Offset of the entry in the file
        /*
 
                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
+
'''Entry names''':
        /*
 
                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
+
!pal - Global palette for 8-bit Indexed Bitmaps.
                $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)
+
0000 - Buildings, props, network intersections,and terrain textures.
        {
 
                $s = "";
 
                $str = fread($fh, 1);
 
                while (ord($str) != 0)
 
                {
 
                        $s .= $str;
 
                        $str = fread($fh, 1);
 
                }
 
  
                return $s;
+
rail - Always used for a rail texture, whereas for street road intersections its always by instance.
 +
 +
TB2  - First sprite animation entry in a directory.
  
        }
+
TB3  - Any sprite animation entries in a directory after TB2.
  
        // 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
+
== FSH Entry Header ==
                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
+
BYTE - Record ID (logically anded by 0x7f for bitmap code or 0x80 to check if the entry is QFS compressed (unused by SC4))
        /*
 
                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
+
INT24 - Size of the block (= width x length + 10h(hex)), Not used for a long time.
                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)
+
UINT16 - Width
        {
 
                $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)
+
UINT16 - Height
        {
 
                $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:
+
UINT16 - X axis coordinate for center of image or for image to spin around. 65535 Max.
        /*
 
                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
+
UINT16 - Y axis coordinate for center of image or for image to spin around. 65535 Max.
        /*
 
                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
+
UINT16 - X axis position to display the image from the left.
                $len - length of compressed string
 
  
        */
+
UINT16 - Y axis position to display the image from the top.
        function decompress($handle, $len) {
 
  
                $buf = '';
 
                $answer = "";
 
                $answerlen = 0;
 
                $numplain = "";
 
                $numcopy = "";
 
                $offset = "";
 
  
                for (;$len>0;) {
+
== Bitmap or palette data ==
  
                        $cc = $this->read_UL1($handle);
+
After the entry header is the bitmap or palette pixel or color information. Palettes are generally arrays of 1 byte each, 256 entries long. Bitmaps may store their pixel data in one of many ways. FSH images can store their pixel data raw, or they can make use of Microsoft DXTC compressed formats.
                        $len -= 1;
 
  
                //      echo $cc."<br />\n";
 
  
                //      printf("      Control char is %02x, len remaining is %08x. \n",$cc,$len);
+
'''Bitmap codes''':
                        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;
 
  
                                /*
+
0x7B - 8-bit indexed Palette: directly follows bitmap or uses global palette Compression: none
                                $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);
+
0x7D- 32-bit A8R8G8B8 Palette: none Compression: none
                                $byte2 = $this->read_UL1($handle);
 
                                $byte3 = $this->read_UL1($handle);
 
  
                                $numplain = $cc & 0x03;
+
0x7F - 24-bit A0R8G8B8 Palette: none Compression: none
                                $numcopy = (($cc & 0x0c) <<6) + 5 + $byte3;
 
                                $offset = (($cc & 0x10) << 12 ) + ($byte1 << 8) + $byte2;
 
                        elseif ($cc >= 128): // 0x80
 
                                $len -= 2;
 
  
                                /*
+
0x7E - 16-bit A1R5G5B5 Palette: none Compression: none
                                $buf = fread($handle, 1);
 
                                $byte1 = unpack("C", $buf);
 
                                $buf = fread($handle, 1);
 
                                $byte2 = unpack("C", $buf);
 
                                */
 
  
                                $byte1 = $this->read_UL1($handle);
+
0x78 - 16-bit A0R5G6B5 Palette: none Compression: none
                                $byte2 = $this->read_UL1($handle);
 
  
                                $numplain = ($byte1 & 0xc0) >> 6;
+
0x6D - 16-bit A4R4G4B4 Palette: none Compression: none
                                $numcopy = ($cc & 0x3f) + 4;
 
                                $offset = (($byte1 & 0x3f) << 8) + $byte2;
 
                        else:
 
                                $len -= 1;
 
  
                                $byte1 = $this->read_UL1($handle);
+
0x61 - DXT3 4x4 packed, alpha premultiplied, Palette: none Compression: 4x4 grid compressed, half-byte per pixel
                                //$buf = fread($handle, 1);
+
                                //$byte1 = unpack("C", $buf);
+
0x60 - DXT1 4x4 packed, 1-bit alpha, Palette: none Compression: 4x4 grid compressed, half-byte per pixel
  
                                $numplain = ($cc & 0x03);
 
                                $numcopy = (($cc & 0x1c) >> 2) + 3;
 
                                $offset = (($cc & 0x60) << 3) + $byte1;
 
                        endif;
 
  
                        #    printf "      plain, copy, offset: $numplain, $numcopy, $offset \n";
+
'''Palette codes''':  
//                      echo "      plain, copy, offset: $numplain, $numcopy, $offset \n";
 
                        $len -= $numplain;
 
  
//                      echo $numplain." -- ".$len."<br />\n";
+
0x22 - 24-bit DOS
  
                        if ($numplain > 0) {
+
0x24 - 24-bit
                                $buf = fread($handle, $numplain);
 
                                $answer = $answer.$buf;
 
                        }
 
  
                        $fromoffset = strlen($answer) - ($offset + 1);  # 0 == last char
+
0x29 - 16-bit NFS5
                        for ($i=0;$i<$numcopy;$i++) {
 
                                $answer = $answer.substr($answer,$fromoffset+$i,1);
 
                        }
 
  
                        $answerlen += $numplain;
+
0x2A - 32-bit
                        $answerlen += $numcopy;
 
                }
 
                //printf "      Answer length is %08x (%08x). \n",$answerlen,length($answer);
 
  
                return $answer;
+
0x2D - 16-bit
        }
 
  
        // 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");
+
'''Text codes''':
                if($handle)
 
                {
 
  
                      echo "Reading from offset: ".$offset."<br />";
+
0x6F - Standard Text file
  
                        $start = $offset;
+
0x69 - ETXT of arbitrary length with full entry header
if ($offset > 0) {
 
                        fseek($handle, $offset);
 
}
 
  
                        $test = fread($handle, 4);  // Should be DBPF
+
0x70 - ETXT of 16 bytes or less including the header
  
if ($test != "DBPF") { echo $test." - This is not a DBPF file!"; return; }
+
0x7C - defined Pixel region Hotspot data for image.
  
                        $this->majorVersion = $this->read_UL4($handle);
 
                        $this->minorVersion = $this->read_UL4($handle);
 
  
                        $this->reserved = fread($handle, 12);
+
This entry can also contain Binary data, however the identifier byte for different binary types anything that isn't already defined with a type so make sure to try and get all codes down correctly. Examples of binary data consist of Palette animations, binary links to outside files, or plain binary data.
  
                        $this->dateCreated = $this->read_UL4($handle);
 
                        $this->dateModified = $this->read_UL4($handle);
 
  
                        $this->indexMajorVersion = $this->read_UL4($handle);
+
== Overview of DXT compression ==
                        $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;
+
Microsofts DirectX Texture Compression uses what they call a 4x4 encoding. Basically, an image must be a multiple of 4 in width and height, because 4x4 blocks of pixels are compressed at a time, similar to encoding used in AVI/MPEG files. Each 4x4 block contains 16 pixels, each pixel using either 24bits or 32bits before compression. All 16 pixels use 512 bits of storage before compression. After compression, that block of 16 pixels is reduced to 64 bits, for an 8:1 compression ratio. The nice thing about DXT compression is its hardware accelerated by nVidia and ATI GPU's all the way back to the GeForce2 and Raedon 8000 series, leaving the CPU free to simulate.
  
                        $this->reserved2 = fread($handle, 32);
+
The compression itself works in the following way. First, all 16 pixels in the 4x4 block are checked, and unique colors stored in a vector (usually just an array of 16 unsigned ints). Once all the pixels in a block are checked, the two color extremes are found among all the unique colors. (This is something I had a hard time with in my own version, so I'm currently just using the method from FSHTool.) These two color extremes make up color1 and color2.
  
                        $headerEnd = ftell($handle);
+
Once color1 and color2 are found, they are reduced from 32bit color to 16bit color (RGB 5:6:5), and stored in the first 32bits of the compressed 64bit chunk of data. The rest of the colors in the 4x4 block are interpolated between thse two colors. Each pixel is only represented by two bits of information, as follows:
  
                        // Seek to index
+
bits    color used for pixel
                        fseek($handle, $offset + $this->indexOffset);
+
---------------------------------
// echo ftell($handle)."\n";
+
00      color1
 +
01      color2
 +
10      2/3 color1 + 1/3 color2
 +
11      1/3 color1 + 2/3 color2
  
                        for ($i=0;$i < $this->indexCount;$i++) {
+
Here is the layout of a 64bit compressed block:
                                $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)));
+
| 16bit RGB (5:6:5) color1  | <- 2 bytes
}
+
|----------------------------|
                                $indexData[$i]['offset'] = $this->read_UL4($handle);
+
| 16bit RGB (5:6:5) color2  | <- 2 bytes
                                $indexData[$i]['filesize'] = $this->read_UL4($handle);
+
|----------------------------|
                                $indexData[$i]['compressed'] = false;
+
|  00  |  01  |  01  | 11  | <- 1 byte, first 4 pixels
                                $indexData[$i]['truesize'] = 0;
+
|----------------------------|
                        }
+
|  01  |  11  |  00  | 11  | <- 1 byte, second 4 pixels
 +
|----------------------------|
 +
|  00  |  01  |  01  | 01  | <- 1 byte, third 4 pixels
 +
|----------------------------|
 +
|  11  |  11  |  00  | 00  | <- 1 byte, fourth 4 pixels
 +
|----------------------------|
  
//echo print_r($indexData, true)."\n";
+
You can see the second part of the block is very basic. Its a simple bitmap of 2 bits each representing 1 of four possible color values. A 00 means use color1, 01 means use color2, 10 means use two-thirds of color1 mixed with one-third of color2, and 11 means use one-third of color1 mixed with two-thirds of color2. This is DXT1 compression, as no alpha information is saved. DXT3 compression uses the same technique, but also stores another 64bits of information for the alpha component of each pixel, in a similar block. So DXT3 compression achieves a 4:1 compression ratio with alpha information included, rather than the 8:1 compression ratio without.
 
 
                        // 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 . "'";
 
                }
 
 
 
        }
 
 
 
 
 
}
 
 
 
?>
 
</pre>
 
 
 
 
 
[[Category:Modding]]
 
[[Category:MTS2]]
 

Revision as of 21:16, 11 October 2011

EA PC SHAPE FORMAT - By Darkmatter and Karybdis


FSH SHPI Header

SHPI (4 bytes)

INT32 - File Size

INT32 - Number of Entries

Directory ID (4 bytes)


Directory ID legal entries:

G354 - Building Textures

G264 - Network Textures, Sim Textures, Sim heads, Sim animations, Trees, props, Base textures, Misc colours

G266 - 3d Animation textures (e.g. the green rotating diamond in loteditor.dat)

G290 - Dispatch marker textures

G315 - Small Sim texture, Network Transport Model Textures (trains etc)

GIMX - UI Editor textures

G344 - BAT gen texture maps


FSH Directory

Entry Name (4 bytes)

INT32 - Offset of the entry in the file


Entry names:

!pal - Global palette for 8-bit Indexed Bitmaps.

0000 - Buildings, props, network intersections,and terrain textures.

rail - Always used for a rail texture, whereas for street road intersections its always by instance.

TB2 - First sprite animation entry in a directory.

TB3 - Any sprite animation entries in a directory after TB2.


FSH Entry Header

BYTE - Record ID (logically anded by 0x7f for bitmap code or 0x80 to check if the entry is QFS compressed (unused by SC4))

INT24 - Size of the block (= width x length + 10h(hex)), Not used for a long time.

UINT16 - Width

UINT16 - Height

UINT16 - X axis coordinate for center of image or for image to spin around. 65535 Max.

UINT16 - Y axis coordinate for center of image or for image to spin around. 65535 Max.

UINT16 - X axis position to display the image from the left.

UINT16 - Y axis position to display the image from the top.


Bitmap or palette data

After the entry header is the bitmap or palette pixel or color information. Palettes are generally arrays of 1 byte each, 256 entries long. Bitmaps may store their pixel data in one of many ways. FSH images can store their pixel data raw, or they can make use of Microsoft DXTC compressed formats.


Bitmap codes:

0x7B - 8-bit indexed Palette: directly follows bitmap or uses global palette Compression: none

0x7D- 32-bit A8R8G8B8 Palette: none Compression: none

0x7F - 24-bit A0R8G8B8 Palette: none Compression: none

0x7E - 16-bit A1R5G5B5 Palette: none Compression: none

0x78 - 16-bit A0R5G6B5 Palette: none Compression: none

0x6D - 16-bit A4R4G4B4 Palette: none Compression: none

0x61 - DXT3 4x4 packed, alpha premultiplied, Palette: none Compression: 4x4 grid compressed, half-byte per pixel

0x60 - DXT1 4x4 packed, 1-bit alpha, Palette: none Compression: 4x4 grid compressed, half-byte per pixel


Palette codes:

0x22 - 24-bit DOS

0x24 - 24-bit

0x29 - 16-bit NFS5

0x2A - 32-bit

0x2D - 16-bit


Text codes:

0x6F - Standard Text file

0x69 - ETXT of arbitrary length with full entry header

0x70 - ETXT of 16 bytes or less including the header

0x7C - defined Pixel region Hotspot data for image.


This entry can also contain Binary data, however the identifier byte for different binary types anything that isn't already defined with a type so make sure to try and get all codes down correctly. Examples of binary data consist of Palette animations, binary links to outside files, or plain binary data.


Overview of DXT compression

Microsofts DirectX Texture Compression uses what they call a 4x4 encoding. Basically, an image must be a multiple of 4 in width and height, because 4x4 blocks of pixels are compressed at a time, similar to encoding used in AVI/MPEG files. Each 4x4 block contains 16 pixels, each pixel using either 24bits or 32bits before compression. All 16 pixels use 512 bits of storage before compression. After compression, that block of 16 pixels is reduced to 64 bits, for an 8:1 compression ratio. The nice thing about DXT compression is its hardware accelerated by nVidia and ATI GPU's all the way back to the GeForce2 and Raedon 8000 series, leaving the CPU free to simulate.

The compression itself works in the following way. First, all 16 pixels in the 4x4 block are checked, and unique colors stored in a vector (usually just an array of 16 unsigned ints). Once all the pixels in a block are checked, the two color extremes are found among all the unique colors. (This is something I had a hard time with in my own version, so I'm currently just using the method from FSHTool.) These two color extremes make up color1 and color2.

Once color1 and color2 are found, they are reduced from 32bit color to 16bit color (RGB 5:6:5), and stored in the first 32bits of the compressed 64bit chunk of data. The rest of the colors in the 4x4 block are interpolated between thse two colors. Each pixel is only represented by two bits of information, as follows:

bits color used for pixel


00 color1 01 color2 10 2/3 color1 + 1/3 color2 11 1/3 color1 + 2/3 color2

Here is the layout of a 64bit compressed block:

|----------------------------| | 16bit RGB (5:6:5) color1 | <- 2 bytes |----------------------------| | 16bit RGB (5:6:5) color2 | <- 2 bytes |----------------------------| | 00 | 01 | 01 | 11 | <- 1 byte, first 4 pixels |----------------------------| | 01 | 11 | 00 | 11 | <- 1 byte, second 4 pixels |----------------------------| | 00 | 01 | 01 | 01 | <- 1 byte, third 4 pixels |----------------------------| | 11 | 11 | 00 | 00 | <- 1 byte, fourth 4 pixels |----------------------------|

You can see the second part of the block is very basic. Its a simple bitmap of 2 bits each representing 1 of four possible color values. A 00 means use color1, 01 means use color2, 10 means use two-thirds of color1 mixed with one-third of color2, and 11 means use one-third of color1 mixed with two-thirds of color2. This is DXT1 compression, as no alpha information is saved. DXT3 compression uses the same technique, but also stores another 64bits of information for the alpha component of each pixel, in a similar block. So DXT3 compression achieves a 4:1 compression ratio with alpha information included, rather than the 8:1 compression ratio without.