• 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
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:
Tibiawiki has something like that. Didn't see which scripting language they're using though.
 
It isn't in PHP it seems...? Just JavaScript. I'd like to do this in PHP.
Also, how do I even get the outfit images? I didn't see any download for it on that page.
Post automatically merged:

I've solved most of the stuff so far, managed to put a background image for my outfit and generate the outfits. But, I need a way to convert CipSoft's Base64(?) outfit code into chunks so I can divide it into like "body", "torso", "legs", "detail", "id".

This is an example of Citizen outfit+color:
pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwYTGRoZWFkGE5kbGVncxg6ZXRvcnNvGEVqZmlyc3RBZGRPbvRiaWQYgGtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZAA

When I decode that as a Base64 string, I just get this:
�emount�ecolor�fdetaildheaddlegsetorsobiddname`foutfit�ecolor�fdetailLdheadNdlegs:etorsoEjfirstAddOn�bid�ksecondAddOn�fsummon�bid

I can see the words like "detail", "head", "legs"... in it. But... the actual values are just... question marks.
Did anyone figure out how to read these values?
 
Last edited:
use another decoder
they're probably using the bytes between words as numbers

Tried the one built-in to JavaScript and in PHP. Still nothing, same results.

Here is the old default outfit, citizen.

Male:
pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwYTGRoZWFkGE5kbGVncxg6ZXRvcnNvGEVqZmlyc3RBZGRPbvRiaWQYgGtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZAA

Female:
pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwYTGRoZWFkGE5kbGVncxg6ZXRvcnNvGEVqZmlyc3RBZGRPbvRiaWQYiGtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZAA

Notice the difference I highlighted. Just a single letter.
 
here you can see the output without the weird symbols
after separating words from bytes, you'll obtain a kind of structure

can't paste it as a text because it breaks post editor so I'll post an image
1626796649503.png

you can use another website to get binary values:

and this to translate:
 
Not able to use the binary converter. Forbidden characters in the decoded base64 string.
What options did you use on all sites?
 
default ones, just remove enter at the end of the textfield

knowing colour ids and outfit looktypes and analysing the differences between generated outfits should give you enough information to figure out the encoding
 
default ones, just remove enter at the end of the textfield

knowing colour ids and outfit looktypes and analysing the differences between generated outfits should give you enough information to figure out the encoding

I tried exactly what you said but nothing works.

I paste this:
pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwYTGRoZWFkGE5kbGVncxg6ZXRvcnNvGEVqZmlyc3RBZGRPbvRiaWQYgGtzZWNvbmRBZGRPbvRmc3VtbW9uoWJpZAA

Into this:

Result:
¤emount¢ecolor¤fdetaildheaddlegsetorsobiddname`foutfit¤ecolor¤fdetailLdheadNdlegs:etorsoEjfirstAddOnôbidksecondAddOnôfsummon¡bid

I then paste that, into this:
And get error.

7mJ4nQD.png


So idk how it can work for you?
What is the end result after you decoded everything?

Can u take images on each page cus I must be doing something wrong?
 
base64 to binary here: Base64 to binary: Encode and decode bytes online (https://cryptii.com/pipes/base64-to-binary)

you can copy the output to view the text here: Binary to Text Converter | Binary Translator (https://www.rapidtables.com/convert/number/binary-to-ascii.html)

the thing I suspect is that the text uses 8 bits for characters, but a different amount of bits for other data. It can be any amount between 2 and 16 (most likely 4 bits and if you get numbers like 10 decimal, try reading in reverse order or try reading it as hex in 0-ff range).
 
I didn't fully decode it.

Save a few outfits with slight differences and use this thread as a reference to figure out how they code the looktypes and colours.
 
I made code to print 'code' as bytes on website and compare them easily:
PHP:
<?php

$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',
];

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

    // skip mount part
//    $data = explode('name', $data, 2)[1];

    echo '<table style="width:100px;float:left">';
    echo '<tr style="height:100px"><td colspan="3">' . $name . '</td></tr>';
    foreach (str_split($data, 1) as $k => $v) {
        echo '<tr>';
        echo '<td>' . $k . '</td>';
        echo '<td>' . ord($v) . '</td>';
        echo '<td>' . $v . '</td>';
        echo '</tr>';
    }
    echo '</table>';
}
First things I found out:
1626944931968.png
Tibia colors 'view': https://ots.me/colors

Numbers notation:
1 byte: '0' = 0
2 bytes: '24' and 'byte value' = byte value
3 bytes: '25' and 2 bytes 'value' = byte1 * 256 + byte2
Anyone know 'retro knight outfit' ID in 12.70 client? From calculation it's 3*256+202 = 970, am I right?
 
Last edited:
I made code to print 'code' as bytes on website and compare them easily:
PHP:
<?php

$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',
];

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

    // skip mount part
//    $data = explode('name', $data, 2)[1];

    echo '<table style="width:100px;float:left">';
    echo '<tr style="height:100px"><td colspan="3">' . $name . '</td></tr>';
    foreach (str_split($data, 1) as $k => $v) {
        echo '<tr>';
        echo '<td>' . $k . '</td>';
        echo '<td>' . ord($v) . '</td>';
        echo '<td>' . $v . '</td>';
        echo '</tr>';
    }
    echo '</table>';
}
First things I found out:
View attachment 60517
Tibia colors 'view': http://ots.me/colors

Here is an example of Retro Knight:

PqOD85H.png


Base64 (Outfit + Colors):
pGVtb3VudKJlY29sb3KkZmRldGFpbABkaGVhZABkbGVncwBldG9yc28AYmlkAGRuYW1lYGZvdXRmaXSkZWNvbG9ypGZkZXRhaWwYXmRoZWFkGHxkbGVncxgmZXRvcnNvGHJqZmlyc3RBZGRPbvRiaWQZA8prc2Vjb25kQWRkT270ZnN1bW1vbqFiaWQA

Base64 (Colors):
pGZkZXRhaWwYXmRoZWFkGHxkbGVncxgmZXRvcnNvGHI

JSON file:
JSON:
        {
            "mount": {
                "color": {
                    "detail": 0,
                    "head": 0,
                    "legs": 0,
                    "torso": 0
                },
                "id": 0
            },
            "name": "RetroTest",
            "outfit": {
                "color": {
                    "detail": 94,
                    "head": 124,
                    "legs": 38,
                    "torso": 114
                },
                "firstAddOn": false,
                "id": 970,
                "secondAddOn": false
            },
            "summon": {
                "id": 0
            }
        }
Post automatically merged:

Yes, it is 970.
 
Back
Top