• 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

Advanced OT User
Joined
Jul 3, 2007
Messages
211
Reaction score
160
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)...
Try with male citizen first, it's 128 and compare it with 129
I got mount ID, outfit ID and colors. Only thing missing is addon enabled/disabled. In JSON there is true/false. In base64 there is value '244' for 'false'. I just need to know what is there when it's 'true'.
 
Anyone here with premium on RL Tibia that has addons ?? :D
I'll try ask someone in English chat if they can send me their code.

@Gesior.pl
How did you get the outfit+colors ?
Post automatically merged:

@Gesior.pl

I asked a player with addon.
This is what I got:

Citizen (Female)
Addon 1+2

pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvViaWQYiGtzZWNvbmRBZGRPbvVmc3VtbW9uoWJpZBkFVg

Addon 1 only
pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvViaWQYiGtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZBkFVg

Addon 2 only
pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwAZGhlYWQAZGxlZ3MYJmV0b3JzbwxqZmlyc3RBZGRPbvRiaWQYiGtzZWNvbmRBZGRPbvVmc3VtbW9uoWJpZBkFVg

It should look like these colors, but with female outfit and addons.
yoY5WS4.png
 
Last edited:
@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:
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