World of Goo – hidden profile file and parsing data

I thought I’d have lost my up-to-date World of Goo profile and thus savegame.

In the end, it was located in Local Settings\Application Data\2DBoy\WorldOfGoo instead of _%APPDATA%_2DBoy\WorldOfGoo.

I already started on a php script to parse, later edit and write the profile file again, after decrypting and afterwards encrypting the pers2.dat file with GooTool.

Anyway, I gladly don’t have to finish it.

Here it is…:

<?php

class WorldOfGooPers2Dat
{
 public $countrycode;            // 11,countrycode2,DE
 public $fullscreen;                // 10,fullscreen4,true | 10,fullscreen4,false
 public $mostRecentProfile;        // 4,mrpp1,0 | 0 = 0..2
 public $profiles;

 public function __construct($countrycode, $fullscreen, $mostRecentProfile, $profiles)
 {
 $this->countrycode            = $countrycode;
 $this->fullscreen            = $fullscreen;
 $this->mostRecentProfile    = $mostRecentProfile;
 $this->profiles                = $profiles;
 }
 public static function createFromString($str)
 {
 // drop "11,countrycode2,", read lang-code (eg EN, DE, etc)
 $countrycode = substr($str, 16, 2);
 $str = substr($str, 18);

 // drop "10,fullscreen4,", read fullscreen boolean
 $fullscreen = (boolean)substr($str, 15, 4);
 $str = substr($str, 19);

 // drop "10,fullscreen4,", read profile id 0..2
 $mostRecentProfile = intval(substr($str, 8, 1));
 $str = substr($str, 9);

 $profiles = array();

 while(substr($str, 0, 3) != ',0.')
 {
 $id = intval(substr($str, 10, 1));
 $str = substr($str, 11);

 $commapos = strpos($str, ',');
 $len = intval(substr($str, 0, $commapos));
 $profiledata = substr($str, $commapos+1, $len);
 $str = substr($str, $commapos+1+$len);

 $profiles[] = Profile::createFromString($id, $profiledata);
 }

 return new self($countrycode, $fullscreen, $mostRecentProfile, $profiles);
 }

 public function toString()
 {
 $txt = ' - countrycode: ' . $this->countrycode . '<br/>'
 .  ' - fullscreen: ' . $this->fullscreen . '<br/>'
 .  ' - mostRecentProfile: ' . $this->mostRecentProfile . '<br/>'
 .  ' - profiles:<br/>';
 foreach($this->profiles AS $profile)
 {
 $txt .= $profile->toString("\t") . '<br/>';
 }
 return $txt;
 }
}

class Profile
{
 public $id;        // 9,profile_0 | 0 = 0..2
 public $playername;    // 
 /**
 * bit-combined integer:
 * 1     online play is enabled
 * 2     World of Goo Corporation has been unlocked
 * 4     World of Goo Corporation has been destroyed
 * 8     the whistle has been found
 * 16     the Terms & Conditions have been accepted (this unlocks Deliverance)
 */
 public $playerflags;    // 
 public $playtime;        // in seconds
 public $numberOfLevelsPC;        // number of levels played/completed

 public $levelInformations;        // list of level information

 public $onlinePlayerKey;
 public $newBalls;

 function __construct($id, $playername, $playerflags, $playtime, $numberOfLevelsPC, $levelInformations, $onlinePlayerKey, $newBalls)
 {
 $this->id                    = $id;
 $this->playername            = $playername;
 $this->playerflags            = $playerflags;
 $this->playtime                = $playtime;
 $this->numberOfLevelsPC        = $numberOfLevelsPC;
 $this->levelInformations    = $levelInformations;

 }
 public static function createFromString($id, $str)
 {
 $data = split(',', $str);
 $playername            = $data[0];
 $playerflags        = $data[1];
 $playtime            = $data[2];
 $numberOfLevelsPC    = $data[3];

 $levelInformations    = array();
 $i = 4;
 for( ; $data[$i] != '0'; $i += 4 )
 {
 $levelInformations[] = new LevelInformation($data[$i], $data[$i+1], $data[$i+2], $data[$i+3]);
 }

 $i++;
 // Goo Corp Data

 $i++;
 $onlinePlayerKey    = $data[$i];
 $i++;
 $newBalls            = $data[$i];

 return new self($id, $playername, $playerflags, $playtime, $numberOfLevelsPC, $levelInformations, $onlinePlayerKey, $newBalls);
 }

 public function toString($prefix='')
 {
 $txt = $prefix . ' - id: ' .                $this->id . '<br/>'
 .  $prefix . ' - playername: ' .        $this->playername . '<br/>'
 .  $prefix . ' - player flags: ' .        $this->playerflags . '<br/>'
 .  $prefix . ' - playtime: ' .            $this->playtime . '<br/>'
 .  $prefix . ' - # levels completed: ' . $this->numberOfLevelsPC . '<br/>'
 .  $prefix . ' - online player key: ' .    $this->onlinePlayerKey . '<br/>'
 .  $prefix . ' - new balls: ' .            $this->newBalls . '<br/>'
 .  $prefix . ' - level information:<br/>';
 foreach($this->levelInformations AS $info)
 {
 $txt .= $info->toString("\t\t") . '<br/>';
 }
 return $txt;
 }
}

class LevelInformation
{
 public $name;
 public $maxballscollected;
 public $leastmoves;
 public $leasttime;

 function __construct($name, $maxballscollected, $leastmoves, $leasttime)
 {
 $this->name = $name;
 $this->maxballscollected = $maxballscollected;
 $this->leastmoves = $leastmoves;
 $this->leasttime = $leasttime;
 }

 public function toString($prefix='')
 {
 return $prefix . $this->name . ' ...';
 }
}

class WorldOfGooCorporation
{
 public $balls;
 public $strands;
}

class Ball
{
 public $type;
 public $pos_x;
 public $pos_y;
 public $vel_x;
 public $vel_y;
}

class Strand
{
 public $type;
 public $startball;
 public $endball;
 public $strength;        // unknown
 public $length;        // unknown
 public $hasAbsorbedBall;

 function __construct($type, $startball, $endball, $strength, $length, $hasAbsorbedBall)
 {
 $this->type                = $type;
 $this->startball        = $startball;
 $this->endball            = $endball;
 $this->strength            = $strength;
 $this->length            = $length;
 $this->hasAbsorbedBall    = $hasAbsorbedBall;
 }
 public static function createFromString($string)
 {
 $values = split(':', $string);

 $type                = $values[0];
 $startball            = $values[1];
 $endball            = $values[2];
 $strength            = $values[3];
 $length                = $values[4];
 $hasAbsorbedBall    = $values[5];

 return Strand($type, $startball, $endball, $strength, $length, $hasAbsorbedBall);
 }
}

$fdata = file_get_contents('pers2.dat.xml');

$dat = WorldOfGooPers2Dat::createFromString($fdata);

echo '<pre>' . $dat->toString() . '</pre>';

?>