• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

PHP PHP Outfitter - But with Tibia's new outfit codes?

222222

Intermediate OT User
Joined
Jul 3, 2007
Messages
207
Reaction score
145
I stumbled upon this thread from a few years ago:

I wonder, is there any way to make it work with Tibia's new outfit codes?
When you open the outfit window in real Tibia after the summer update, there are a few new buttons to generate an outfit / color code.
And then it generates a Base64 code like this:

Outfit + Colors:
pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwYTGRoZWFkGE5kbGVncxg6ZXRvcnNvGEVqZmlyc3RBZGRPbvRiaWQYgGtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZAA

Just the colors:
pGZkZXRhaWwYTGRoZWFkGE5kbGVncxg6ZXRvcnNvGEU

My request is to be able to pass these into the GET parameter of the outfitter script, as seen above in the other thread.

Example:

I also have no clue how to export the latest outfits from the new Tibia client. But I'd honestly just be satisfied with the regular free+premium outfits. I need it for a project that I am working on.

1626790318541.png
 
Solution
@222222
I got first version of 'code reader'
Number parsing:
Code:
(bytes) -> (number value)
below 24 -> value
24, 5 -> 5
25, 1, 23 -> 1 * 256 + 23 = 279
PHP class and example code that reads mount and outfit data:
Code:
<?php

class OutfitCodeReader
{
    /** @var resource */
    private $data;
    /** @var array */
    private $outfitData = [];
    /** @var array */
    private $mountData = [];

    /**
     * @param string $code
     */
    public function parseOutfitData($code)
    {
        $this->mountData = [];
        $this->outfitData = [];

        try {
            $base64Code = base64_decode($code);
            $this->data = fopen('php://memory', 'wb+');
            fwrite($this->data, $base64Code)...
@222222
I got first version of 'code reader'
Number parsing:
Code:
(bytes) -> (number value)
below 24 -> value
24, 5 -> 5
25, 1, 23 -> 1 * 256 + 23 = 279
PHP class and example code that reads mount and outfit data:
Code:
<?php

class OutfitCodeReader
{
    /** @var resource */
    private $data;
    /** @var array */
    private $outfitData = [];
    /** @var array */
    private $mountData = [];

    /**
     * @param string $code
     */
    public function parseOutfitData($code)
    {
        $this->mountData = [];
        $this->outfitData = [];

        try {
            $base64Code = base64_decode($code);
            $this->data = fopen('php://memory', 'wb+');
            fwrite($this->data, $base64Code);
            $this->length = ftell($this->data);
            rewind($this->data);

            $ignoredByte1 = $this->getByte();
            $stringMount = $this->getString();
            $ignoredByte2 = $this->getByte();
            $stringMountColor = $this->getString();
            $ignoredByte3 = $this->getByte();

            // detail
            $this->mountData[$this->getString()] = $this->getNumber();
            // head
            $this->mountData[$this->getString()] = $this->getNumber();
            // legs
            $this->mountData[$this->getString()] = $this->getNumber();
            // torso
            $this->mountData[$this->getString()] = $this->getNumber();
            // id
            $this->mountData[$this->getString()] = $this->getNumber();
            // name
            $this->mountData[$this->getString()] = $this->getString();

            $stringOutfit = $this->getString();
            $ignoredByte4 = $this->getByte();
            $stringOutfitColor = $this->getString();
            $ignoredByte5 = $this->getByte();

            // detail
            $this->outfitData[$this->getString()] = $this->getNumber();
            // head
            $this->outfitData[$this->getString()] = $this->getNumber();
            // legs
            $this->outfitData[$this->getString()] = $this->getNumber();
            // torso
            $this->outfitData[$this->getString()] = $this->getNumber();
            // firstAddOn
            $this->outfitData[$this->getString()] = $this->getBool();
            // id
            $this->outfitData[$this->getString()] = $this->getNumber();
            // secondAddOn
            $this->outfitData[$this->getString()] = $this->getBool();

            return true;
        } catch (RuntimeException $exception) {
            return false;
        }
    }

    public function getMountData()
    {
        return $this->mountData;
    }

    public function getOutfitData()
    {
        return $this->outfitData;
    }

    /**
     * @return int
     */
    private function getByte()
    {
        return unpack('C', fread($this->data, 1))[1];
    }

    /**
     * @return bool
     */
    private function getBool()
    {
        $byte = $this->getByte();

        if ($byte === 244) {
            return false;
        } elseif ($byte === 245) {
            return true;
        } else {
            throw new RuntimeException('Parser error.');
        }
    }

    /**
     * @return int
     */
    private function getNumber()
    {
        $byte = $this->getByte();

        if ($byte < 24) {
            return $byte;
        } elseif ($byte === 24) {
            return $this->getByte();
        } elseif ($byte === 25) {
            return $this->getByte() * 256 + $this->getByte();
        } else {
            throw new RuntimeException('Parser error.');
        }
    }

    /**
     * @return string
     */
    private function getString()
    {
        $stringLength = $this->getByte() - 96;
        if ($stringLength === 0) {
            return '';
        } elseif ($stringLength < 0) {
            throw new RuntimeException('Parser error.');
        }

        return fread($this->data, $stringLength);
    }
}

$codes = [
    'Mage outfit, no mount' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYhGV0b3JzbxiEamZpcnN0QWRkT270YmlkGIJrc2Vjb25kQWRkT270ZnN1bW1vbqFiaWQA',
    'Paladin outfit, with store moun' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkGQJzZG5hbWVgZm91dGZpdKRlY29sb3KkZmRldGFpbABkaGVhZABkbGVncxiEZXRvcnNvGIRqZmlyc3RBZGRPbvRiaWQYgWtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZAA',
    'Retro knight (store outfit), no mount' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYhGV0b3JzbxiEamZpcnN0QWRkT270YmlkGQPKa3NlY29uZEFkZE9u9GZzdW1tb26hYmlkAA',
    'Retro knight (store outfit), with mount' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkGQJzZG5hbWVgZm91dGZpdKRlY29sb3KkZmRldGFpbABkaGVhZABkbGVncxiEZXRvcnNvGIRqZmlyc3RBZGRPbvRiaWQZA8prc2Vjb25kQWRkT270ZnN1bW1vbqFiaWQA',
    'citizen female no addon' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvViaWQYiGtzZWNvbmRBZGRPbvVmc3VtbW9uoWJpZBkFVg',
    'citizen female 1 addon' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvViaWQYiGtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZBkFVg',
    'citizen female 2 addon' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvRiaWQYiGtzZWNvbmRBZGRPbvVmc3VtbW9uoWJpZBkFVg',
];

$outfitCodeReader = new OutfitCodeReader();

foreach ($codes as $name => $code) {

    if ($outfitCodeReader->parseOutfitData($code)) {
        var_dump($outfitCodeReader->getMountData());
        /*
        array (size=6)
          'detail' => int 0
          'head' => int 0
          'legs' => int 0
          'torso' => int 0
          'id' => int 0
          'name' => string '' (length=0)
         */
        var_dump($outfitCodeReader->getOutfitData());
        /*
        array (size=7)
          'detail' => int 0
          'head' => int 0
          'legs' => int 132
          'torso' => int 132
          'firstAddOn' => boolean false
          'id' => int 130
          'secondAddOn' => boolean false
         */
    } else {
        echo 'failed to load code, probably modified by player';
    }
}
Example result:
Code:
array (size=6)
  'detail' => int 0
  'head' => int 0
  'legs' => int 0
  'torso' => int 0
  'id' => int 0
  'name' => string '' (length=0)

array (size=7)
  'detail' => int 0
  'head' => int 0
  'legs' => int 132
  'torso' => int 132
  'firstAddOn' => boolean false
  'id' => int 970
  'secondAddOn' => boolean false
 
Last edited:
Solution
@222222
I got first version of 'code reader', but I found some special color value '12' in codes you posted above. It makes number parsing even more weird:
Code:
(bytes) -> (number value)
0 -> 0
12 -> 12? (not checked)
24, 5 -> 5
25, 1, 23 -> 1 * 256 + 23 = 279
PHP class and example code that reads mount and outfit data:
Code:
<?php

class OutfitCodeReader
{
    /** @var resource */
    private $data;
    /** @var array */
    private $outfitData = [];
    /** @var array */
    private $mountData = [];

    /**
     * @param string $code
     */
    public function parseOutfitData($code)
    {
        $this->mountData = [];
        $this->outfitData = [];

        try {
            $base64Code = base64_decode($code);
            $this->data = fopen('php://memory', 'wb+');
            fwrite($this->data, $base64Code);
            $this->length = ftell($this->data);
            rewind($this->data);

            $ignoredByte1 = $this->getByte();
            $stringMount = $this->getString();
            $ignoredByte2 = $this->getByte();
            $stringMountColor = $this->getString();
            $ignoredByte3 = $this->getByte();

            // detail
            $this->mountData[$this->getString()] = $this->getNumber();
            // head
            $this->mountData[$this->getString()] = $this->getNumber();
            // legs
            $this->mountData[$this->getString()] = $this->getNumber();
            // torso
            $this->mountData[$this->getString()] = $this->getNumber();
            // id
            $this->mountData[$this->getString()] = $this->getNumber();
            // name
            $this->mountData[$this->getString()] = $this->getString();

            $stringOutfit = $this->getString();
            $ignoredByte4 = $this->getByte();
            $stringOutfitColor = $this->getString();
            $ignoredByte5 = $this->getByte();

            // detail
            $this->outfitData[$this->getString()] = $this->getNumber();
            // head
            $this->outfitData[$this->getString()] = $this->getNumber();
            // legs
            $this->outfitData[$this->getString()] = $this->getNumber();
            // torso
            $this->outfitData[$this->getString()] = $this->getNumber();
            // firstAddOn
            $this->outfitData[$this->getString()] = $this->getBool();
            // id
            $this->outfitData[$this->getString()] = $this->getNumber();
            // secondAddOn
            $this->outfitData[$this->getString()] = $this->getBool();

            return true;
        } catch (RuntimeException $exception) {
            return false;
        }
    }

    public function getMountData()
    {
        return $this->mountData;
    }

    public function getOutfitData()
    {
        return $this->outfitData;
    }

    /**
     * @return int
     */
    private function getByte()
    {
        return unpack('C', fread($this->data, 1))[1];
    }

    /**
     * @return bool
     */
    private function getBool()
    {
        $byte = $this->getByte();

        if ($byte === 244) {
            return false;
        } elseif ($byte === 245) {
            return true;
        } else {
            throw new RuntimeException('Parser error.');
        }
    }

    /**
     * @return int
     */
    private function getNumber()
    {
        $byte = $this->getByte();

        if ($byte === 0) {
            return 0;
        } elseif ($byte === 12) {
            // TODO: check if that value is valid and if there are any other 'magic' values
            return 12;
        } elseif ($byte === 24) {
            return $this->getByte();
        } elseif ($byte === 25) {
            return $this->getByte() * 256 + $this->getByte();
        } else {
            throw new RuntimeException('Parser error.');
        }
    }

    /**
     * @return string
     */
    private function getString()
    {
        $stringLength = $this->getByte() - 96;
        if ($stringLength === 0) {
            return '';
        } elseif ($stringLength < 0) {
            throw new RuntimeException('Parser error.');
        }

        return fread($this->data, $stringLength);
    }
}

$codes = [
    'Mage outfit, no mount' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYhGV0b3JzbxiEamZpcnN0QWRkT270YmlkGIJrc2Vjb25kQWRkT270ZnN1bW1vbqFiaWQA',
    'Paladin outfit, with store moun' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkGQJzZG5hbWVgZm91dGZpdKRlY29sb3KkZmRldGFpbABkaGVhZABkbGVncxiEZXRvcnNvGIRqZmlyc3RBZGRPbvRiaWQYgWtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZAA',
    'Retro knight (store outfit), no mount' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYhGV0b3JzbxiEamZpcnN0QWRkT270YmlkGQPKa3NlY29uZEFkZE9u9GZzdW1tb26hYmlkAA',
    'Retro knight (store outfit), with mount' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkGQJzZG5hbWVgZm91dGZpdKRlY29sb3KkZmRldGFpbABkaGVhZABkbGVncxiEZXRvcnNvGIRqZmlyc3RBZGRPbvRiaWQZA8prc2Vjb25kQWRkT270ZnN1bW1vbqFiaWQA',
    'citizen female no addon' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvViaWQYiGtzZWNvbmRBZGRPbvVmc3VtbW9uoWJpZBkFVg',
    'citizen female 1 addon' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvViaWQYiGtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZBkFVg',
    'citizen female 2 addon' => 'pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvRiaWQYiGtzZWNvbmRBZGRPbvVmc3VtbW9uoWJpZBkFVg',
];

$outfitCodeReader = new OutfitCodeReader();

foreach ($codes as $name => $code) {
    $data = base64_decode($code);

    if ($outfitCodeReader->parseOutfitData($code)) {
        var_dump($outfitCodeReader->getMountData());
        /*
        array (size=6)
          'detail' => int 0
          'head' => int 0
          'legs' => int 0
          'torso' => int 0
          'id' => int 0
          'name' => string '' (length=0)
         */
        var_dump($outfitCodeReader->getOutfitData());
        /*
        array (size=7)
          'detail' => int 0
          'head' => int 0
          'legs' => int 132
          'torso' => int 132
          'firstAddOn' => boolean false
          'id' => int 130
          'secondAddOn' => boolean false
         */
    } else {
        echo 'failed to load code, probably modified by player';
    }
}
Example result:
Code:
array (size=6)
  'detail' => int 0
  'head' => int 0
  'legs' => int 0
  'torso' => int 0
  'id' => int 0
  'name' => string '' (length=0)

array (size=7)
  'detail' => int 0
  'head' => int 0
  'legs' => int 132
  'torso' => int 132
  'firstAddOn' => boolean false
  'id' => int 970
  'secondAddOn' => boolean false

Amazing! You are fantastic, Gesior. How much do I owe you?
 
I noticed an issue with a regular citizen outfit.

"Eternal Oblivion".

pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwYOmRoZWFkGChkbGVncwlldG9yc28HamZpcnN0QWRkT270YmlkGIBrc2Vjb25kQWRkT270ZnN1bW1vbqFiaWQA

It should look like this:
bOFOnNo.png




Edit: did further testing. A lot of colors won't work (especially upper-left corner) for Citizen outfit.
 
Last edited:
I noticed an issue with a regular citizen outfit.

"Eternal Oblivion".



It should look like this:
bOFOnNo.png




Edit: did further testing. A lot of colors won't work (especially upper-left corner) for Citizen outfit.
My new idea number format:
Every number below '24' is directly number value. '24' means that next byte is number value and '25' means that 2 next bytes are number value.
Replace function getNumber from my post above with:
PHP:
    private function getNumber()
    {
        $byte = $this->getByte();

        if ($byte < 24) {
            return $byte;
        } elseif ($byte === 24) {
            return $this->getByte();
        } elseif ($byte === 25) {
            return $this->getByte() * 256 + $this->getByte();
        } else {
            throw new RuntimeException('Parser error.');
        }
    }

Parsed your code on my host with function above:

EDIT:
Updated post marked as 'answer' code.
 
Last edited:
My new idea number format:
Every number below '24' is directly number value. '24' means that next byte is number value and '25' means that 2 next bytes are number value.
Replace function getNumber from my post above with:
PHP:
    private function getNumber()
    {
        $byte = $this->getByte();

        if ($byte < 24) {
            return $byte;
        } elseif ($byte === 24) {
            return $this->getByte();
        } elseif ($byte === 25) {
            return $this->getByte() * 256 + $this->getByte();
        } else {
            throw new RuntimeException('Parser error.');
        }
    }

Parsed your code on my host with function above:

Works like a charm. Thank you so much!
The outfitter works perfectly on my site now.
You're a genius!

Now I will try to extract some new looktypes (outfits) and then I am done.
I am still using the old looktypes (8.6 or so, not really sure).
 
Back
Top