• 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!

Understanding tibia .spr file format from tibia 7.4

Shadowox

Active Member
Joined
Nov 9, 2018
Messages
46
Solutions
1
Reaction score
37
GitHub
DiegoRibeiro
Greetings,

I am trying to creating a simple program to extract the sprites from the Tibia.spr. From the original tibia client 7.4.

I have found this explanation but cant find find the transparent color 255 0 255, the value doesnt match:

[4] File Version
[2] Sprite Count
----Now we have sets of 4-bytes that contains the offset for each sprite.
[4] Contains the offset of the first sprite
[4] Contains the offset of the second sprite
[4] Contains the offset of the third sprite
----This continues for each sprite. (The number in Sprite Count.)
----After all of the offsets we start the data for the first sprite.
----Each sprite begins with the transparent-pixels' color. In this case, the Tibia client uses magenta so the three bytes returned are 255, 0, 255. You don't need this pixel when creating the bitmap.
[1] 255
[1] 0
[1] 255
----Note that I only tested that on maybe 10 sprites. So you may have to do more research to verify this.
----Now we get into the actual pixels for this sprite. (This is just an example sprite.)
[2] Number of transparent pixels
[2] Number of colored pixels (for this example we'll say it's 3)
[1] Red value of the first colored pixel
[1] Green value of the first colored pixel
[1] Blue value of the first colored pixel
[1] Red value of the second colored pixel
[1] Green value of the second colored pixel
[1] Blue value of the second colored pixel
[1] Red value of the third colored pixel
[1] Green value of the third colored pixel
[1] Blue value of the third colored pixel
[2] Number of transparent pixels
[2] Number of colored pixels
----Repeat for all transparent and colored pixels in the sprite. Once finished it will start at the next sprite, beginning with the 3 bytes I mentioned before.

I already found the spr file version which is 1102703238.
The number of sprites is 9999.
But the first and second offset doesnt point to the right place, am i doing something wrong? ill post the code.
 
Last edited:
Solution
After many days trying, i finally extracted the sprites. This code doesnt use any library. I created my own bitmap file.

A few notes, i should upgrade the code for more portability. Because not every system has a unsigned int with 32bits. But will do this later with the library stdint.h from the c99 standard.

Hope this material helps someone and i think its a good material to study.

Here some links that i use for research:
BMP file format - Wikipedia
Tibia.dat Reader + .dat/.spr Structure and .spr reading code link

Thanks to
@Jo3Bingham
For creating the thread that helped me a lot.


FINAL CODE.

Feel free to ask anything about the code, and ill try to explain.
Code:
#include <stdio.h>
#include <stdlib.h>...
Yes i saw your post and really appreciate. Another question, Do you still have the code that you linked in your post? Because the link is broken. I find it odd that sprites have diferent size, i thought it was all 32x32.

And the transparent pixel that i am finding is F8 F8 F0. Is this right?

Really appreciate, if i succed in extract the sprites ill post a detailed post about it and will share my code.

ps: I am using Tibia.spr 7.4.

Finally i did it. I'll update my working code.
 
Last edited by a moderator:
After many days trying, i finally extracted the sprites. This code doesnt use any library. I created my own bitmap file.

A few notes, i should upgrade the code for more portability. Because not every system has a unsigned int with 32bits. But will do this later with the library stdint.h from the c99 standard.

Hope this material helps someone and i think its a good material to study.

Here some links that i use for research:
BMP file format - Wikipedia
Tibia.dat Reader + .dat/.spr Structure and .spr reading code link

Thanks to
@Jo3Bingham
For creating the thread that helped me a lot.


FINAL CODE.

Feel free to ask anything about the code, and ill try to explain.
Code:
#include <stdio.h>
#include <stdlib.h>
#include<windows.h>
// #include <stdint.h> // to specify a 32 bit

#pragma pack(1)
typedef struct bmp_header {
   unsigned short header_field;
   unsigned int size;
   unsigned short reserved_1;
   unsigned short reserved_2;
   unsigned int offset_data;
} BMP_HEADER;

typedef struct dib_header {
   unsigned int header_size;
   int width;
   int height;
   unsigned short color_plane;
   unsigned short color_depth;
   unsigned int compression;
   unsigned int raw_size_img;
   unsigned int h_res;
   unsigned int v_res;
   unsigned int number_color;
   unsigned int important_color;
} DIB_HEADER;

typedef struct v4 {
   unsigned int red_mask;
   unsigned int green_mask;
   unsigned int blue_mask;
   unsigned int alpha_mask;
   unsigned int windows_color_space;
   unsigned char color_space_endpoints[36];
   unsigned int red_gamma;
   unsigned int green_gamma;
   unsigned int blue_gamma;
}BMP_V4;

typedef struct tibiaspr {
   unsigned int version;
   unsigned short count;
} TIBIA_SPRITE_HEADER;

typedef struct pixel {
   unsigned char r;
   unsigned char g;
   unsigned char b;
} PIXEL;
#pragma pack()

void setup_header_bmp(BMP_HEADER* bh, DIB_HEADER* dh, BMP_V4* v4) {
   // bitmap header 14 bytes
   bh->header_field = 0x4D42;            // BM
   bh->size = 0x107A;                   // Size of the BMP file NEED MOD
   bh->reserved_1 = 0x0;
   bh->reserved_2 = 0x0;
   bh->offset_data = 0x7A;               // where the data starts its fixed
   
   // dib header 40 bytes
   dh->header_size = 0x6C;               // 108 bytes
   dh->width = 0x20;                   // width of bitmap NEED MOD
   dh->height = 0xFFFFFFE0;           // height of bitmap NEED MOD
   dh->color_plane = 0x1;
   dh->color_depth = 0x20;
   dh->compression = 0x3;
   dh->raw_size_img = 0x1000;           // raw size including padding NEED MOD
   dh->h_res = 0x130B;
   dh->v_res = 0x130B;
   dh->number_color = 0x0;
   dh->important_color = 0x0;
   
   // header bitmap 32 bits v4 68 bytes
   v4->red_mask            =   0x00FF0000;
   v4->green_mask            =   0x0000FF00;
   v4->blue_mask            =   0x000000FF;
   v4->alpha_mask           =   0xFF000000;
   v4->windows_color_space =    0x206E6957;
   
   for(int i = 0; i < 36; i++) {
       v4->color_space_endpoints[i] = 0x0;
   }
   
   v4->red_gamma = 0x0;
   v4->green_gamma = 0x0;
   v4->blue_gamma = 0x0;
}


int main (int argc, char** argv) {
   
   FILE *fp = fopen("Tibia.spr", "rb");

   
   TIBIA_SPRITE_HEADER tsh;
   unsigned int* offsets = NULL;
   unsigned int spr_addr_offset;
   unsigned char r, g, b;
   int i;
   char buffer[50];
   
   BMP_HEADER bh;
   DIB_HEADER dh;
   BMP_V4 v4;
   
   unsigned char alpha_0 = 0x0;
   unsigned char alpha_1 = 0xFF;
   
   CreateDirectory("./output", NULL);
   
   fread(&tsh, sizeof(TIBIA_SPRITE_HEADER), 1, fp);
   printf("Sprite version: %u\n", tsh.version);
   printf("Sprite count: %u\n", tsh.count);
   
   for(int i = 0; i < tsh.count; i++) {
       unsigned int addr_offset = 6 + (4*i);
       fseek(fp, addr_offset, SEEK_SET);
       
       unsigned int offset;
       fread(&offset, sizeof(unsigned int), 1, fp);
       
       if (offset == 0) { // reserved, its empty and dont point to anything
           continue;
       }
       
       printf("Processing sprite number %u ...\n", i);
       sprintf(buffer, "./output/sprite%u.bmp", i);
       printf("Create file %s\n", buffer);
       FILE * fpi = fopen (buffer, "wb");
       printf("offset %x\n", offset);
       
       fseek(fp, offset, SEEK_SET);
       PIXEL tpix;
       fread(&tpix, sizeof(PIXEL), 1, fp);
       tpix.r = 0xFF;
       tpix.g = 0x0;
       tpix.b = 0xFF;
       printf("Found transparent pixel with value: (%u, %u, %u)\n", tpix.r, tpix.g, tpix.b);
       unsigned short bytes_to_proccess;
       fread(&bytes_to_proccess, sizeof(unsigned short), 1, fp);
       printf("processing %u bytes ...\n", bytes_to_proccess);
       
       if (bytes_to_proccess == 0) {
           setup_header_bmp(&bh, &dh, &v4);
           fwrite(&bh, sizeof(BMP_HEADER), 1, fpi);
           fwrite(&dh, sizeof(DIB_HEADER), 1, fpi);
           fwrite(&v4, sizeof(BMP_V4), 1, fpi);
           
           // criar um sprite vazio
           for(int j = 0; j < 1024; j++) { // https://stackoverflow.com/questions/2601365/padding-in-24-bits-rgb-bitmap
               // need to put a pad every row
               // B G R
               fwrite(&tpix.b, sizeof(unsigned char), 1, fpi);
               fwrite(&tpix.g, sizeof(unsigned char), 1, fpi);
               fwrite(&tpix.r, sizeof(unsigned char), 1, fpi);
               fwrite(&alpha_0, sizeof(unsigned char), 1, fpi);
           }
       }
       else {   
           setup_header_bmp(&bh, &dh, &v4);
           fwrite(&bh, sizeof(BMP_HEADER), 1, fpi);
           fwrite(&dh, sizeof(DIB_HEADER), 1, fpi);
           fwrite(&v4, sizeof(BMP_V4), 1, fpi);       
           int byte_con = bytes_to_proccess;
           int imgsize = 0;
           while (byte_con > 0) {
               printf("bytes left = %d\n", byte_con);
               //getchar();
               unsigned short c_tpix = 0;   // count transparent pixels
               unsigned short c_cpix = 0;   // count colored pixels
           
               fread(&c_tpix, sizeof(unsigned short), 1, fp);
               printf("Found %d  transparent pixel\n", c_tpix);
               fread(&c_cpix, sizeof(unsigned short), 1, fp);
               printf("Found %d colored pixel\n", c_cpix);
               
               if(c_tpix > 1024 || c_cpix > 1024) {
                   if(imgsize < 1024) {
                       for(int j = 0; j < 1024 - imgsize; j++) {
                           fwrite(&tpix.b, sizeof(unsigned char), 1, fpi);
                           fwrite(&tpix.g, sizeof(unsigned char), 1, fpi);
                           fwrite(&tpix.r, sizeof(unsigned char), 1, fpi);
                           fwrite(&alpha_1, sizeof(unsigned char), 1, fpi);
                       }
                   }
                   break;
               }
               
               imgsize += c_tpix + c_cpix;
               printf("found so far %u\n", imgsize);
               
               byte_con -= sizeof(unsigned short);
               byte_con -= (c_cpix * 3);
               
               for(int j = 0; j < c_tpix; j++) {
                   fwrite(&tpix.b, sizeof(unsigned char), 1, fpi);
                   fwrite(&tpix.g, sizeof(unsigned char), 1, fpi);
                   fwrite(&tpix.r, sizeof(unsigned char), 1, fpi);
                   fwrite(&alpha_1, sizeof(unsigned char), 1, fpi);
               }
               
               unsigned char r;
               unsigned char g;
               unsigned char b;
               PIXEL aux;
               for(int j = 0; j < c_cpix; j++) {
                   fread(&r, sizeof(unsigned char), 1, fp);
                   fread(&g, sizeof(unsigned char), 1, fp);
                   fread(&b, sizeof(unsigned char), 1, fp);
                   
                   fwrite(&b, sizeof(unsigned char), 1, fpi);
                   fwrite(&g, sizeof(unsigned char), 1, fpi);
                   fwrite(&r, sizeof(unsigned char), 1, fpi);
                   fwrite(&alpha_1, sizeof(unsigned char), 1, fpi);
               }
               
               //getchar();
               
           }
           printf("Finished ... \n");
       }
       
       fclose(fpi);
       
       
       //getchar();
   }
   
   return 0;
   
   fclose(fp);
   
   return 0;
}
 
Last edited:
Solution
After many days trying, i finally extracted the sprites. This code doesnt use any library. I created my own bitmap file.

A few notes, i should upgrade the code for more portability. Because not every system has a unsigned int with 32bits. But will do this later with the library stdint.h from the c99 standard.

Hope this material helps someone and i think its a good material to study.

Here some links that i use for research:
BMP file format - Wikipedia
Tibia.dat Reader + .dat/.spr Structure and .spr reading code link

Thanks to
@Jo3Bingham
For creating the thread that helped me a lot.


FINAL CODE.

Feel free to ask anything about the code, and ill try to explain.
Code:
#include <stdio.h>
#include <stdlib.h>
#include<windows.h>
// #include <stdint.h> // to specify a 32 bit

#pragma pack(1)
typedef struct bmp_header {
   unsigned short header_field;
   unsigned int size;
   unsigned short reserved_1;
   unsigned short reserved_2;
   unsigned int offset_data;
} BMP_HEADER;

typedef struct dib_header {
   unsigned int header_size;
   int width;
   int height;
   unsigned short color_plane;
   unsigned short color_depth;
   unsigned int compression;
   unsigned int raw_size_img;
   unsigned int h_res;
   unsigned int v_res;
   unsigned int number_color;
   unsigned int important_color;
} DIB_HEADER;

typedef struct v4 {
   unsigned int red_mask;
   unsigned int green_mask;
   unsigned int blue_mask;
   unsigned int alpha_mask;
   unsigned int windows_color_space;
   unsigned char color_space_endpoints[36];
   unsigned int red_gamma;
   unsigned int green_gamma;
   unsigned int blue_gamma;
}BMP_V4;

typedef struct tibiaspr {
   unsigned int version;
   unsigned short count;
} TIBIA_SPRITE_HEADER;

typedef struct pixel {
   unsigned char r;
   unsigned char g;
   unsigned char b;
} PIXEL;
#pragma pack()

void setup_header_bmp(BMP_HEADER* bh, DIB_HEADER* dh, BMP_V4* v4) {
   // bitmap header 14 bytes
   bh->header_field = 0x4D42;            // BM
   bh->size = 0x107A;                   // Size of the BMP file NEED MOD
   bh->reserved_1 = 0x0;
   bh->reserved_2 = 0x0;
   bh->offset_data = 0x7A;               // where the data starts its fixed
  
   // dib header 40 bytes
   dh->header_size = 0x6C;               // 108 bytes
   dh->width = 0x20;                   // width of bitmap NEED MOD
   dh->height = 0xFFFFFFE0;           // height of bitmap NEED MOD
   dh->color_plane = 0x1;
   dh->color_depth = 0x20;
   dh->compression = 0x3;
   dh->raw_size_img = 0x1000;           // raw size including padding NEED MOD
   dh->h_res = 0x130B;
   dh->v_res = 0x130B;
   dh->number_color = 0x0;
   dh->important_color = 0x0;
  
   // header bitmap 32 bits v4 68 bytes
   v4->red_mask            =   0x00FF0000;
   v4->green_mask            =   0x0000FF00;
   v4->blue_mask            =   0x000000FF;
   v4->alpha_mask           =   0xFF000000;
   v4->windows_color_space =    0x206E6957;
  
   for(int i = 0; i < 36; i++) {
       v4->color_space_endpoints[i] = 0x0;
   }
  
   v4->red_gamma = 0x0;
   v4->green_gamma = 0x0;
   v4->blue_gamma = 0x0;
}


int main (int argc, char** argv) {
  
   FILE *fp = fopen("Tibia.spr", "rb");

  
   TIBIA_SPRITE_HEADER tsh;
   unsigned int* offsets = NULL;
   unsigned int spr_addr_offset;
   unsigned char r, g, b;
   int i;
   char buffer[50];
  
   BMP_HEADER bh;
   DIB_HEADER dh;
   BMP_V4 v4;
  
   unsigned char alpha_0 = 0x0;
   unsigned char alpha_1 = 0xFF;
  
   CreateDirectory("./output", NULL);
  
   fread(&tsh, sizeof(TIBIA_SPRITE_HEADER), 1, fp);
   printf("Sprite version: %u\n", tsh.version);
   printf("Sprite count: %u\n", tsh.count);
  
   for(int i = 0; i < tsh.count; i++) {
       unsigned int addr_offset = 6 + (4*i);
       fseek(fp, addr_offset, SEEK_SET);
      
       unsigned int offset;
       fread(&offset, sizeof(unsigned int), 1, fp);
      
       if (offset == 0) { // reserved, its empty and dont point to anything
           continue;
       }
      
       printf("Processing sprite number %u ...\n", i);
       sprintf(buffer, "./output/sprite%u.bmp", i);
       printf("Create file %s\n", buffer);
       FILE * fpi = fopen (buffer, "wb");
       printf("offset %x\n", offset);
      
       fseek(fp, offset, SEEK_SET);
       PIXEL tpix;
       fread(&tpix, sizeof(PIXEL), 1, fp);
       tpix.r = 0xFF;
       tpix.g = 0x0;
       tpix.b = 0xFF;
       printf("Found transparent pixel with value: (%u, %u, %u)\n", tpix.r, tpix.g, tpix.b);
       unsigned short bytes_to_proccess;
       fread(&bytes_to_proccess, sizeof(unsigned short), 1, fp);
       printf("processing %u bytes ...\n", bytes_to_proccess);
      
       if (bytes_to_proccess == 0) {
           setup_header_bmp(&bh, &dh, &v4);
           fwrite(&bh, sizeof(BMP_HEADER), 1, fpi);
           fwrite(&dh, sizeof(DIB_HEADER), 1, fpi);
           fwrite(&v4, sizeof(BMP_V4), 1, fpi);
          
           // criar um sprite vazio
           for(int j = 0; j < 1024; j++) { // https://stackoverflow.com/questions/2601365/padding-in-24-bits-rgb-bitmap
               // need to put a pad every row
               // B G R
               fwrite(&tpix.b, sizeof(unsigned char), 1, fpi);
               fwrite(&tpix.g, sizeof(unsigned char), 1, fpi);
               fwrite(&tpix.r, sizeof(unsigned char), 1, fpi);
               fwrite(&alpha_0, sizeof(unsigned char), 1, fpi);
           }
       }
       else {  
           setup_header_bmp(&bh, &dh, &v4);
           fwrite(&bh, sizeof(BMP_HEADER), 1, fpi);
           fwrite(&dh, sizeof(DIB_HEADER), 1, fpi);
           fwrite(&v4, sizeof(BMP_V4), 1, fpi);      
           int byte_con = bytes_to_proccess;
           int imgsize = 0;
           while (byte_con > 0) {
               printf("bytes left = %d\n", byte_con);
               //getchar();
               unsigned short c_tpix = 0;   // count transparent pixels
               unsigned short c_cpix = 0;   // count colored pixels
          
               fread(&c_tpix, sizeof(unsigned short), 1, fp);
               printf("Found %d  transparent pixel\n", c_tpix);
               fread(&c_cpix, sizeof(unsigned short), 1, fp);
               printf("Found %d colored pixel\n", c_cpix);
              
               if(c_tpix > 1024 || c_cpix > 1024) {
                   if(imgsize < 1024) {
                       for(int j = 0; j < 1024 - imgsize; j++) {
                           fwrite(&tpix.b, sizeof(unsigned char), 1, fpi);
                           fwrite(&tpix.g, sizeof(unsigned char), 1, fpi);
                           fwrite(&tpix.r, sizeof(unsigned char), 1, fpi);
                           fwrite(&alpha_1, sizeof(unsigned char), 1, fpi);
                       }
                   }
                   break;
               }
              
               imgsize += c_tpix + c_cpix;
               printf("found so far %u\n", imgsize);
              
               byte_con -= sizeof(unsigned short);
               byte_con -= (c_cpix * 3);
              
               for(int j = 0; j < c_tpix; j++) {
                   fwrite(&tpix.b, sizeof(unsigned char), 1, fpi);
                   fwrite(&tpix.g, sizeof(unsigned char), 1, fpi);
                   fwrite(&tpix.r, sizeof(unsigned char), 1, fpi);
                   fwrite(&alpha_1, sizeof(unsigned char), 1, fpi);
               }
              
               unsigned char r;
               unsigned char g;
               unsigned char b;
               PIXEL aux;
               for(int j = 0; j < c_cpix; j++) {
                   fread(&r, sizeof(unsigned char), 1, fp);
                   fread(&g, sizeof(unsigned char), 1, fp);
                   fread(&b, sizeof(unsigned char), 1, fp);
                  
                   fwrite(&b, sizeof(unsigned char), 1, fpi);
                   fwrite(&g, sizeof(unsigned char), 1, fpi);
                   fwrite(&r, sizeof(unsigned char), 1, fpi);
                   fwrite(&alpha_1, sizeof(unsigned char), 1, fpi);
               }
              
               //getchar();
              
           }
           printf("Finished ... \n");
       }
      
       fclose(fpi);
      
      
       //getchar();
   }
  
   return 0;
  
   fclose(fp);
  
   return 0;
}


I have made a enhanced version fixing some bugs of this code and create a better structure on my github feel free to visit:

 
Back
Top