1

I want to reverse engineer titltext.cel .CEL image format and render this in SDL2. I happen to know a little bit about the CEL file that I am working with. I know it is supposed to be a sprite-sheet of the alphabet and some symbols.

The .CEL image format is a format that is very stripped with very little header information.

Googleing leads me to a few .CEL Specifications .CEL Specifications 2 documents which seem to have the idea , but I think is lacking for loading more than a few frames. From the documentation I can kind of tell how many frames are in a CEL image and where the first frame starts.

Knowing where the first frame starts I thought I could bypass the stripped image header

IMAGE HEADER:

37 00 00 00 E4 00 00 00 78 03 00 00 D6 05 00 00 
18 08 00 00 B2 0A 00 00 02 0D 00 00 F8 0E 00 00 
9D 11 00 00 07 14 00 00 4D 15 00 00 CB 16 00 00 
04 19 00 00 9E 1A 00 00 1E 1E 00 00 ED 20 00 00 
59 24 00 00 4C 26 00 00 78 29 00 00 E7 2B 00 00 
1C 2E 00 00 18 30 00 00 AF 32 00 00 EC 34 00 00 
BF 38 00 00 88 3B 00 00 8F 3D 00 00 EE 3F 00 00 
4B 41 00 00 8E 43 00 00 D6 45 00 00 B4 47 00 00 
06 4A 00 00 28 4C 00 00 E9 4D 00 00 4C 50 00 00 
67 52 00 00 A9 55 00 00 09 57 00 00 D6 58 00 00 
37 5C 00 00 19 5F 00 00 56 60 00 00 F4 61 00 00 
A1 63 00 00 5D 64 00 00 93 65 00 00 B6 66 00 00 
59 67 00 00 6A 68 00 00 7F 69 00 00 86 6A 00 00 
70 6B 00 00 13 6C 00 00 A2 6C 00 00 8A 6E 00 00 
45 70 00 00

After the Image Header / Start of the first frame.

D2 D2 D2 D2 D2 D2 D2 D2 D2 F9 03 F5 F4 F4 F3 03 F4 F4 F5
EC FD 07 F5 F5 F5 F5 F5 ED ......

>

As per documentation , the first DWORD is the amount of FRAMES in the .CEL . This is 0x37 which is 55 in decimal which seems accurate. From what I understand the rest is just frame locations . Each frame from what I understand is supposed to have a Width of 45 and a Height 46 Pixels. Since the image is 8 bit colour this means it would be 1 byte for each colour.

Also, I have heard that these old image types are called palettes or something to help alter the colour.

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_mixer.h>
#include <stdbool.h>
#include <stdio.h>
#include <errno.h>


SDL_Window *window; // SDL MAIN WINDOW
SDL_Renderer *renderer;
SDL_Texture *texture;


int SCREEN_WIDTH = 1920;
int SCREEN_HEIGHT = 1080;
SDL_Event e;
bool quit = false;


/** 32-bit in-memory backbuffer surface */
SDL_Surface *surface;
/** 8-bit surface wrapper around #gpBuffer */
SDL_Surface *pal_surface;
/** Currently active palette */
SDL_Palette *palette;
SDL_Texture* texture;


FILE* CELFILE;

unsigned char CELBuffer [3000];

// The CEL dementions I am trying to open are 
// W 46 Height 45 Width 55 FRAMES
void LoadCEL (char * Path){


CELFILE = fopen(Path, "rb"); 


int CELHEADERSIZE = 228;
int CELFRAMESIZE  = 2070;    

//The Header Appears to be 228 bytes in size.  I want to put a frame into CELBuffer 
//  
// I am assuming to get the size of the first framee I need a size of 2017 (45 * 55) ;

    unsigned char c;

    for (int i = 0;  i < (CELFRAMESIZE+CELHEADERSIZE) ; i++){
        if(i > CELHEADERSIZE ){
        c  = fgetc(CELFILE);

        CELBuffer[i] = c;
        printf("CELFILE HEADR %02x \n" ,CELBuffer[i]  & 0xff);

        }

    }   

    void *  pCELBuffer = CELBuffer; 
    surface = SDL_CreateRGBSurfaceFrom( pCELBuffer , 45, 55, 8,1 ,0, 0, 0, 0xff); 
    texture = SDL_CreateTextureFromSurface(renderer, surface);

}

void Create_SDL_Window()
{

    SDL_Init(SDL_INIT_EVERYTHING);
    IMG_Init(IMG_INIT_PNG);
        window = SDL_CreateWindow("Test Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    renderer = SDL_CreateRenderer(window, -1, 0);
    printf("Window And Renderer Created!\n");
}


int main(){

LoadCEL("/home/james/Desktop/titltext.cel");

Create_SDL_Window();
printf("THIS WORKD\n");


while (!quit){  
        SDL_RenderPresent(renderer);


while (SDL_PollEvent(&e)){
    //If user closes the window
    if (e.type == SDL_QUIT){
        quit = true;
    }
    //If user presses any key
    if (e.type == SDL_KEYDOWN){
    //  quit = true;
    }
    //If user clicks the mouse
    if (e.type == SDL_MOUSEBUTTONDOWN){
    /// quit = true;
        }
    }   
    SDL_RenderClear(renderer);
    //renderTexture(image, renderer, x, y);
    SDL_RenderPresent(renderer);

}

}

Any help is much appreciated.

1

Won't be able to tell you what exactly is the CEL format but from what I can see:

  • the offsets into frames suggest that frames are about 600 bytes long (0x378-0xe4=660, 0x5d6-0x378=606, etc)
  • since the image dimensions are 45*46=2070, each frame must be compressed to fit to 600 bytes
  • it's an old game (and as you mentioned using a palette), after the decompression one pixel is represented as one byte. Each byte is an index into a palette table.
  • palette table doesn't seem to be stored in the CEL file and the game stores the palette separately. Palette is an array with length 256 elements, each element storing 3 values for R, G and B. Complete palette is 768 bytes but it's possible the game stores only subsets of the full palette. It can be that e.g. indices 1-127 are for sprites and 128-255 are for background. There's no hard rule how a game decides to use palette. It's also possible that each level uses a different palette. Search for PAL files or files 768 bytes long, they might be the right palettes for you.
  • the first documentation describes how to decompress the frame, follow paragraph "5.3 Commands"

Commands in you case:

  • 0xd2: 0x100-0xd2=46 transparent pixels (46 is the width so the full scanline is transparent)
  • 0xd2: 0x100-0xd2=46 transparent pixels
  • ...
  • 0xf9: 0x100-0xf9=7 transparent pixels, this is an incomplete scanline, more pixels to follow...
  • 0x03: block command, not sure based on the doc, but it looks like that 3+1 bytes are pixel data (`0xf5, 0xf4, 0xf4, 0xd4). All block commands aremy guess and I might be completely wrong.
  • 0x03: block command, 3+1 bytes are pixel data (0xf4, 0xf4, 0xf5, 0xec)
  • 0xfd: 0x100-0xfd=3 transparent pixels
  • 0x07: block command?, 7+1 bytes are pixel data (0xf5, ...,0xf3`)
  • 0x06: block command?, 6+1 bytes are pixel data (0xf4, ...,0xef`)
  • 0xfe: 0x100-0xfe=2 transparent pixels
  • etc.

Once you manage to process all commands, you have the indices into that palette table. Even if you don't have the palette you can pretend it's a grayscale image and render the image like that. You should be able to recognise the shape of sprites.

The decompression algorithm, especially the block commands seem to be the tricky bits so probably what you could do is to download some Diablo editors and try to supply your own sprites to see how your sprites get compressed.

If that fails or it's too much work you can always disassemble the game or even the editors to see how they decompress CEL files.

EDIT: This tool seems to be loading CEL files and would help you understand the format. See readFrame() and readFrame2() functions. These don't seems to care about block commands but at the same time don't seem to work 100% with your file (just reading the source code). Looks like you have plenty to investigate. :)

It also contains a palette file you could use.

  • Awesome work. I think I understand this better. I will try to knock it out. – Qndel Oct 30 '18 at 18:49

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.