# Source for file SwfCompiler.php

Documentation is available at SwfCompiler.php

1. <?php
3. /**
4. * SWF Compiler class for SWX.
5. * Collection of methods that compile a SWX SWF.
6. * Released under the GNU GPL license.
7. *
8. * Author: Aral Balkan
9. * Copyright (c) 2007 Aral Balkan.
10. * http://aralbalkan.com
11. *
12. * http://swxformat.org
13. */
15. // PHP 5 compatibility layer for PHP 4
16. require_once('lib/str_split.php');
18. // Constants
19. define ('LITTLE_ENDIAN', 'little-endian');
20. define ('BIG_ENDIAN', 'big-endian');
21. define ('BI_ENDIAN', 'bi-endian');
23. class SwfCompiler
24. {
25. var $stack = '';
26. var $endian = NULL;
28. function SwfCompiler()
29. {
30. global $endian;
32. // Determine endianness of the system that this is running under
33. // Adapted from: http://phpfer.com/rn45re877.html
34. $ab = 0x6162;
36. // Convert $ab to a binary string containing 32 bits
37. // Do the conversion the way that the system architecture wants to
38. switch (pack ('L', $ab))
39. {
40. // Compare the value to the same value converted in a Little-Endian fashion
41. case pack ('V', $ab):
42. $endian = LITTLE_ENDIAN;
43. break;
45. // Compare the value to the same value converted in a Big-Endian fashion
46. // TODO: Test on a big-endian machine. Currently SWX is not supported on
47. // big endian machines.
48. case pack ('V', $ab):
49. $endian = BIG_ENDIAN;
50. break;
52. // Bi-endian or middle endian. The biggest use case for this is
53. // PowerPC architectures. In fact, take this to mean PowerPC support.
54. default:
55. $endian = BI_ENDIAN;
56. }
58. error_log ('[SWX] INFO Running on a '.$endian.' system.');
59. }
61. // From http://de.php.net/var_dump
62. function getVarDump($mixed = null)
63. {
64. ob_start();
65. var_dump($mixed);
66. $content = ob_get_contents();
67. ob_end_clean();
68. return $content;
69. }
71. /**
72. * Converts the passed object to arrays.
73. *
74. * @return void
75. * @author Aral Balkan
76. ***/
77. function objectToArray($data='')
78. {
79. $arr = array();
80. foreach ($data as $key => $value )
81. {
82. if (gettype($value) == 'object')
83. {
84. $arr[$key] = $this->objectToArray($value);
85. }
86. else
87. {
88. $arr[$key] = $value;
89. }
90. }
91. return $arr;
92. }
94. /**
95. * Parses the given data structure (any type) and
96. * returns SWF bytecode.
97. *
98. * @param any A PHP data structure
99. * @return string Swf bytecode representation of the data structure.
100. * @author Aral Balkan
101. ***/
102. function dataToBytecode ($data)
103. {
104. $type = gettype($data);
106. switch($type)
107. {
108. case 'array':
109. $bytecode = $this->arrayToBytecode($data);
110. break;
112. case 'object':
113. // TODO: Handle objects natively without
114. // converting to an associative array.
116. // Convert object to array
117. $data = $this->objectToArray($data);
119. // And then use the array encoder
120. $bytecode = $this->arrayToBytecode($data);
121. break;
123. case 'string':
124. $bytecode = $this->stringToBytecode($data);
125. break;
127. case 'integer':
128. $bytecode = $this->integerToBytecode($data);
129. break;
131. case 'double':
132. $bytecode = $this->doubleToBytecode($data);
133. break;
135. case 'boolean':
136. $bytecode = $this->booleanToBytecode($data);
137. break;
139. case 'NULL':
140. $bytecode = DATA_TYPE_NULL;
141. break;
143. default:
144. trigger_error('Unhandled data type ('.$type.')', E_USER_ERROR);
145. //error_log('[SWX] ERROR dataToBytecode() Unhandled data type: ' . $type);
146. //$bytecode = "ERROR";
147. }
149. return $bytecode;
150. }
153. /**
154. * Converts the passed array to SWF bytecode.
155. *
156. * @return string SWF bytecode.
157. * @author Aral Balkan
158. ***/
159. function arrayToBytecode($arr)
160. {
161. // Note: We are going to write the bytecode backwards,
162. // starting from the end as it's easier that way.
164. $bytecode = '';
166. // To count number of elements in the array
167. $arrCount = count($arr);
169. // Determine array type
170. $keys = array_keys($arr);
172. if ($arrCount == 0 || gettype($keys[0]) == 'integer')
173. {
174. // Reverse the array to mirror how they're stored in a SWF (this
175. // won't affect associative arrays (objects in Flash) but it will
176. // make regular arrays appear in the right order.
177. // $arr = array_reverse($arr);
178. $arrayType = ARRAY_TYPE_REGULAR;
179. $bytecode = ACTION_INIT_ARRAY . $bytecode;
180. }
181. else
182. {
183. $arrayType = ARRAY_TYPE_ASSOCIATIVE;
184. $bytecode = ACTION_INIT_OBJECT . $bytecode;
185. }
187. // Add the number of elements
188. //
189. // Optimized:
190. // DATA_TYPE_INTEGER . strtoupper(str_pad($this->makeLittleEndian(dechex($arrCount)), 8, '0')) . $bytecode;
191. //
192. // Clear:
193. // $this->integerToBytecode($arrCount) . $bytecode;
194. //
195. $bytecode = $this->integerToBytecode($arrCount) . $bytecode;
197. // Profiling:
198. //$forLoopStartTime = $this->microtime_float();
200. foreach ($arr as $key => $value)
201. {
202. // Check if the current bytecode length is approaching
203. // the 64K (65535 byte) limit that we can store in a single push
204. // and, if so, create a new push.
206. // TODO: Refactor - pull out
207. $bytecodeLenInDec = strlen($bytecode)/2;
208. if ($bytecodeLenInDec >= 65520) // For testing use >= 2)
209. {
210. // Calculate bytecode length *without* counting the
211. // init object or init array action
212. $lastInstruction = substr($bytecode, -2);
213. if ( $lastInstruction == ACTION_INIT_OBJECT || $lastInstruction == ACTION_INIT_ARRAY)
214. {
215. //error_log('(at end) IS COMPLEX - '.$lastInstruction);
216. $bytecodeLenInDec -= 1;
217. }
219. // TODO: Refactor - pull out
220. $bytecodeLenInHex = $this->getIntAsHex($bytecodeLenInDec, 2);
222. $bytecode = $bytecodeLenInHex . $bytecode;
223. $bytecode = '96' . $bytecode; // Push. TODO: Replace w. constant
225. // Store current instruction on the stack
226. $this->stack = $bytecode . $this->stack;
228. // Reset the bytecode
229. $bytecode = '';
230. }
232. // Convert objects to arrays.
233. // TODO: Handle objects natively.
234. if (gettype($value) == 'object')
235. {
236. $value = $this->objectToArray($value);
237. }
239. // Is this a simple data type or an array?
240. if (gettype($value) == 'array')
241. {
242. // Element is an array, we need to *push* it on to
243. // the stack separately. End the current push.
244. // (Note: this produces bytecode that differs from
245. // what the Adobe compiler in Flash produces but
246. // it's easier.)
248. $bytecodeLenInDec = strlen($bytecode)/2;
250. // Calculate bytecode length *without* counting the
251. // init object or init array action
252. $lastInstruction = substr($bytecode, -2);
254. if ( $lastInstruction == ACTION_INIT_OBJECT || $lastInstruction == ACTION_INIT_ARRAY)
255. {
256. //error_log('IS COMPLEX - '.$lastInstruction);
257. $bytecodeLenInDec -= 1;
258. }
260. // If we haven't written any bytecode into the local
261. // buffer yet (if it's empty), don't write a push statement.
262. if ($bytecodeLenInDec != -1 && $bytecodeLenInDec != 0)
263. {
264. // TODO: Refactor - pull out
265. $bytecodeLenInHex = $this->getIntAsHex($bytecodeLenInDec, 2);
266. $bytecode = $bytecodeLenInHex . $bytecode;
267. $bytecode = '96' . $bytecode; // Push. TODO: Replace w. constant
268. }
270. // Store current instruction on the stack
271. $this->stack = $bytecode . $this->stack;
273. // Reset the bytecode
274. $bytecode = '';
276. // Add the found array to the stack
277. $this->arrayToBytecode($value);
279. // If this is an element from an associative array, push the
280. // key before you recurse:
281. if ($arrayType == ARRAY_TYPE_ASSOCIATIVE)
282. {
283. $bytecode = $this->dataToBytecode($key) . $bytecode;
284. }
286. }
287. else
288. {
289. // Simple data type
291. // What type of array are we?
292. if ($arrayType == ARRAY_TYPE_REGULAR)
293. {
294. // Regular
295. $bytecode = $this->dataToBytecode($value).$bytecode;
296. }
297. else
298. {
299. // Associative (in Flash: object)
300. $bytecode = $this->dataToBytecode($key)
301. .$this->dataToBytecode($value)
302. .$bytecode;
303. }
304. }
305. }
307. // Profiling:
308. // error_log("For loop took" . (microtime_float()-$forLoopStartTime));
311. if ($bytecode != '')
312. {
313. $bytecodeLenInDec = strlen($bytecode)/2;
315. // TODO: Refactor - Pull out
317. // Calculate bytecode length *without* counting the
318. // init object or init array action
319. $lastInstruction = substr($bytecode, -2);
320. if ( $lastInstruction == ACTION_INIT_OBJECT || $lastInstruction == ACTION_INIT_ARRAY)
321. {
322. //error_log('(at end) IS COMPLEX - '.$lastInstruction);
323. $bytecodeLenInDec -= 1;
324. }
326. // TODO: Refactor - pull this out into its own method now that
327. // it is being used at the start of the loop also.
328. $bytecodeLenInHex = $this->getIntAsHex($bytecodeLenInDec, 2);
329. $bytecode = $bytecodeLenInHex . $bytecode;
330. $bytecode = '96' . $bytecode; // Push. TODO: Replace w. constant
332. // Store current instruction on the stack
333. $this->stack = $bytecode . $this->stack;
334. }
335. else
336. {
337. //error_log('Bytecode is empty, skipping...');
338. }
340. //error_log('Returning stack: ' . $this->stack);
342. return $this->stack;
343. }
346. /**
347. * Converts the passed string to hex and returns the correct SWF bytecode for it.
348. *
349. * @param string The string to convert to bytecode.
350. * @return string SWF bytecode for the passed string
351. * @author Aral Balkan
352. ***/
353. function stringToBytecode ($str)
354. {
355. $bytecode = strtoupper(DATA_TYPE_STRING . $this->strhex($str) . NULL_TERMINATOR);
357. return $bytecode;
358. }
361. /**
362. * Converts the passed integer to bytecode, padding it to
363. * $numBytes bytes in little-endian.
364. *
365. * @param integer Number to convert to hex byte representation.
366. * @param integer Number of bytes to pad to.
367. * @return string Integer as hex string.
368. * @author Aral Balkan
369. ***/
370. function integerToBytecode($int)
371. {
372. $bytecode = DATA_TYPE_INTEGER . $this->getIntAsHex($int, 4);
373. return $bytecode;
374. }
377. /**
378. * Converts a double to its IEEE 754 representation (little-endian)
379. *
380. * Modified from Chung Leong's function.
381. * (http://www.thescripts.com/forum/thread9237.html.)
382. *
383. * TODO: Works on 5.1.2 for me (OS X 10.4.9, Intel Core Duo, MAMP 1.2.1) but
384. * waiting for report back from Alex Skinner for whom it is not working. Weird!
385. *
386. * @param double A PHP double.
387. * @return string Little-endian Flash double in hex (variation on IEEE 754).
388. * @author Aral Balkan
389. ***/
390. function doubleToBytecode($f)
391. {
392. global $endian;
394. $f = (double) $f;
395. $b = pack("d", $f);
396. $hex = "";
398. // This test is here for PowerPC Macs which are bi-endian.
399. if ($endian == BI_ENDIAN)
400. {
401. $b = strrev($b);
402. }
404. for($i = 0; $i < strlen($b); $i++)
405. {
406. $c = ord($b{$i});
408. $hex .= sprintf("%02X", $c);
409. }
411. $hex = substr($hex, 8, 8).substr($hex, 0, 8);
413. return DATA_TYPE_DOUBLE . $hex;
414. }
417. /**
418. * Converts the passed boolean to SWF bytecode.
419. *
420. * @return string Boolean as SWF bytecode.
421. * @author Aral Balkan
422. ***/
423. function booleanToBytecode ($bool)
424. {
425. $boolBytecode = DATA_TYPE_BOOLEAN . ($bool ? '01':'00');
426. return $boolBytecode;
427. }
430. /**
431. * Wraps the SWF buffer in a doAction block, wraps that with
432. * the SWF Header and the SWF footer (set variable, show frame, end SWF)
433. * and writes out the SWF.
434. *
435. * @param Data to write out as a SWF.
436. * @return void
437. * @author Aral Balkan
438. ***/
439. function writeSwf($data, $debug = false, $compressionLevel = 4, $url = '')
440. {
441. global $allowDomain;
443. // If the passed data structure is not an array,
444. // convert it to one.
445. if (gettype($data) != 'array')
446. {
447. // Result is not an array, making it so.
448. $data = array($data);
449. }
451. // Check data root
452. if (gettype($data) == 'array')
453. {
454. $pushTag = $this->arrayToBytecode($data);
456. // Add the 'data' variable name -- either
457. // using the constant table if in debug mode
458. // or as a regular string otherwise
459. if ($debug)
460. {
461. $pushTag = '9602000800' . $pushTag;
462. }
463. else
464. {
465. $pushTag = '96080000726573756C7400' . $pushTag;
466. }
467. }
469. // Create the DoAction tag
470. $doActionBlock = $pushTag;
472. $doActionBlock .= ACTION_SET_VARIABLE;
474. // Allow domain? If so add allow domain statement to the SWF
475. if ($allowDomain === true)
476. {
477. error_log("[SWX] INFO Allow domain is on.");
479. $doActionBlock = $doActionBlock . $this->getAllowDomainBytecode($url); // ALLOW_DOMAIN;
480. }
482. // Debug? If so, add the analyzer connector to the SWF
483. if ($debug)
484. {
485. error_log('[SWX] INFO Debug mode is on.');
487. $doActionBlock = DEBUG_START . $doActionBlock . DEBUG_END;
488. }
490. $doActionBlockSizeInBytes = $this->getStringLengthInBytesHex($doActionBlock, 4);
491. $doActionBlock = ACTION_DO_ACTION . $doActionBlockSizeInBytes . $doActionBlock;
493. // Create the whole SWF
495. $headerType = ($compressionLevel > 0) ? COMPRESSED_SWF : UNCOMPRESSED_SWF;
497. $swf = $headerType . HEADER . $doActionBlock . ACTION_SHOW_FRAME . ACTION_END_SWF;
498. $swfSizeInBytes = $this->getStringLengthInBytesHex($swf, 4);
499. $swf = str_replace('LLLLLLLL', $swfSizeInBytes, $swf);
501. // Convert the SWF bytecode to a string (file)
502. $swfFile = $this->hexstr ($swf);
504. // Stats
505. $uncompressedSize = strlen($swfFile);
506. error_log('[SWX] INFO Uncompressed size of SWF: ' . $uncompressedSize . ' bytes.');
508. // Compress the SWF if required
509. // Profiling info: Performance impact of compression is negligible.
510. if ($compressionLevel > 0)
511. {
512. $compressionStartTime = $this->microtime_float();
514. // The first eight bytes are uncompressed
515. $uncompressedBytes = substr($swfFile, 0, 8);
517. // Remove first eight bytes
518. $swfFile = substr_replace($swfFile, '', 0, 8);
520. // Compress the rest of the SWF
521. $swfFile = gzcompress($swfFile, $compressionLevel);
523. // Add the uncompressed header
524. $swfFile = $uncompressedBytes . $swfFile;
526. $compressionDuration = $this->microtime_float() - $compressionStartTime;
527. error_log('[SWX] PROFILING: SWF compression took ' . $compressionDuration . ' seconds.');
529. // Stats
530. $compressedSize = strlen($swfFile);
531. error_log('[SWX] INFO Compressed size of SWF: ' . $compressedSize . ' bytes.');
532. }
534. header("Content-Type: application/swf;");
535. header('Content-Disposition: attachment; filename="data.swf"');
536. header('Content-Length: ' . strlen($swfFile));
537. echo $swfFile;
539. // Enable the next line to write out a hex representation of
540. // the SWF to the error log (helps with testing.)
541. // error_log($this->prettyHex($swf));
542. }
544. function getAllowDomainBytecode($url)
545. {
546. if ($url === '')
547. {
548. // No URL passed -- possibly called by legacy code, use the old _parent._url version.
549. error_log('[SWX] INFO: No URL passed from client. Defaulting to old behavior. You must call System.security.allowDomain on the dataHolder for cross domain data loading to work.');
550. return ALLOW_DOMAIN;
551. }
553. error_log('[SWX] INFO: Data SWF will allow access from ' . $url);
555. // URL is passed, write that into the returned code
557. $allowDomainBytecode = $this->stringToBytecode($url);
559. // The -13 is to accomodate the other elements being pushed to the
560. // stack in the hard-coded part of the bytecode.
561. $allowDomainBytecodeLengthDec = strlen($allowDomainBytecode)/2 + 13;
563. $allowDomainBytecodeLength = $this->getIntAsHex($allowDomainBytecodeLengthDec, 2);
564. $allowDomainBytecode = '96' . $allowDomainBytecodeLength . $allowDomainBytecode . SYSTEM_ALLOW_DOMAIN;
566. return $allowDomainBytecode;
567. }
570. /**
571. * Helper methods. (For data type conversions, formatting, etc.)
572. */
574. // Returns a string with the length of the passed hex string in bytes
575. // padded to display in $numBytes bytes.
576. function getStringLengthInBytesHex($strInHex, $numBytes)
577. {
578. // Divide length in chars by 2 to get length in bytes
579. $bytecodeLenInDec = strlen($strInHex)/2;
581. $bytecodeLenInHex = $this->getIntAsHex($bytecodeLenInDec, $numBytes);
583. return $bytecodeLenInHex;
584. }
587. /**
588. * Returns the hexadecimal representation of the passed integer,
589. * padded to $numBytes bytes in little-endian.
590. *
591. * @param integer Number to convert to hex byte representation.
592. * @param integer Number of bytes to pad to.
593. * @return string Integer as hex string.
594. * @author Aral Balkan
595. ***/
596. function getIntAsHex($int, $numBytes=1)
597. {
598. $intAsHex = strtoupper(str_pad($this->makeLittleEndian(dechex($int)), $numBytes*2, '0'));
599. return $intAsHex;
600. }
602. //////////////////////////////////////////////////////////////////////
603. //
604. // makeLittleEndian()
605. //
606. // Takes a hex string in big endian and coverts it to little endian
607. //
608. //////////////////////////////////////////////////////////////////////
609. function makeLittleEndian($str)
610. {
611. $sLen = strlen($str);
613. // Make sure that the string is padded to the byte boundary
614. if ($sLen%2 == 1)
615. {
616. $sLen++;
617. $str = '0'.$str;
618. }
620. $sLenInBytes = $sLen/2;
622. $strArr = str_split($str, 2);
624. $strArr = array_reverse($strArr);
625. $strLittleEndian = implode('', $strArr);
627. return $strLittleEndian;
628. }
630. //////////////////////////////////////////////////////////////////////
631. //
632. // prettyHex()
633. //
634. // Pretty prints hex string in 1 bytes groups, 10 to a line
635. // and show number of bytes in the string.
636. //
637. //////////////////////////////////////////////////////////////////////
638. function prettyHex ($h)
639. {
640. $pretty = "\n\n 01 02 03 04 05 06 07 08 09 10\n -----------------------------\n0001| ";
641. $hArr = str_split($h, 2);
643. $lineCount = 1;
645. for ($i = 0; $i < count($hArr); $i++)
646. {
647. $pretty .= $hArr[$i] . ' ';
648. if (($i+1)%10 == 0 )
649. {
650. $lineCount++;
651. $pretty .= "\n".str_pad($lineCount, 4, "0", STR_PAD_LEFT).'| ';
652. }
653. }
654. $pretty .= "\n\n$h\n\nNum bytes: ".count($hArr)."\n";
656. return $pretty;
657. }
659. //////////////////////////////////////////////////////////////////////
660. //
661. // debug()
662. //
663. // Debug only displays debug messages if we
664. // are not writing out a SWF.
665. //
666. //////////////////////////////////////////////////////////////////////
667. function debug($str)
668. {
669. global $isPost;
670. if ($isPost || isset($_GET['swf']))
671. {
672. return;
673. }
674. else
675. {
676. echo $str;
677. }
678. }
681. /**
682. * Converts a string of hexadecimal values to a string of ASCII characters.
683. *
684. * @return string String with ASCII characters
685. * @author Paul Gregg <pgregg@pgregg.com>
686. * @link http://www.pgregg.com/projects/php/code/hexstr.phps
687. ***/
688. function hexstr($hexstr)
689. {
690. $hexstr = str_replace(' ', '', $hexstr);
691. $retstr = pack('H*', $hexstr);
692. return $retstr;
693. }
696. /**
697. * Converts a string of ASCII characters to a string of hexadecimal byte values.
698. *
699. * @return string String with ASCII characters
700. * @author Paul Gregg <pgregg@pgregg.com>
701. * @author Aral Balkan (added PHP4 bug fix)
702. * @link http://www.pgregg.com/projects/php/code/hexstr.phps
703. ***/
704. function strhex($string)
705. {
706. $hexstr = unpack('H*', $string);
708. // Fix for unpack bug
709. // http://bugs.php.net/bug.php?id=36148
710. // PHP 4 and 5 appear to give different results for the unpack
711. // PHP 4.4.3+ exhibits same behavior as PHP 5.
712. // PHP version 5.1.2 exhibits the same behavior as PHP 4.
713. // Tested with: 4.4.2, 4.4.3, 4.4.4, 5.1.4, 5.1.6, 5.2.1.
714. // Definitely *not* supported on 4.3.10 (or the 4.3 branch at all.)
715. //
716. // TODO: Look for a way to optimize this.
718. $phpVer = phpversion();
720. if ( (substr($phpVer,0,1) == '4' && intval(substr($phpVer,4, 1)) < 3) || $phpVer == '5.1.2')
721. {
722. // PHP 4
723. return substr($hexstr[1], 0, -1);
724. }
725. else
726. {
727. // PHP 4.4.3+ and 5.1.4+
728. return array_shift($hexstr);
729. }
730. }
732. // Profiling
733. function microtime_float()
734. {
735. list($usec, $sec) = explode(" ", microtime());
736. return ((float)$usec + (float)$sec);
737. }
740. }
742. //
743. // SWF bytecode constants. Discovered through observation.
744. //
746. // Header - FCS (uncompressed), version Flash 6
747. define('UNCOMPRESSED_SWF', '46');
748. define('COMPRESSED_SWF', '43');
749. define('HEADER', '575306LLLLLLLL300A00A0000101004302FFFFFF');
751. // Action bytecodes
752. define('ACTION_PUSH', '96LLLL');
753. define('ACTION_SHOW_FRAME', '4000');
754. define('ACTION_END_SWF', '0000');
755. define('ACTION_SET_VARIABLE', '1D'); // 00
756. define('ACTION_DO_ACTION', '3F03');
757. define('ACTION_INIT_ARRAY', '42');
758. define('ACTION_INIT_OBJECT', '43');
760. // Data type codes
761. define('DATA_TYPE_STRING', '00');
762. define('DATA_TYPE_NULL', '02');
763. define('DATA_TYPE_BOOLEAN', '05');
764. define('DATA_TYPE_DOUBLE', '06');
765. define('DATA_TYPE_INTEGER', '07');
767. // Misc
768. define('NULL_TERMINATOR', '00');
770. // Non-bytecode constants
771. define('ARRAY_TYPE_REGULAR', 'regular');
772. define('ARRAY_TYPE_ASSOCIATIVE', 'associative');
774. // Allow domain (*)
775. define('ALLOW_DOMAIN', '960900005F706172656E74001C960600005F75726C004E960D0007010000000053797374656D001C960A00007365637572697479004E960D0000616C6C6F77446F6D61696E005217');
777. define('SYSTEM_ALLOW_DOMAIN', '07010000000053797374656D001C960A00007365637572697479004E960D0000616C6C6F77446F6D61696E005217');
779. // Debug SWX bytecode. Creates a local connection to the SWX Debugger front-end.)
781. define('DEBUG_START','883C000700726573756C74006C63004C6F63616C436F6E6E656374696F6E005F737778446562756767657200636F6E6E6563740064656275670073656E6400');
783. define('DEBUG_END', '960D0008010600000000000000000802403C9609000803070100000008011C9602000804521796020008001C960500070100000042960B0008050803070300000008011C96020008065217');
785. ?>

Documentation generated on Fri, 06 Jul 2007 19:54:52 +0100 by phpDocumentor 1.3.1