Assets - Converting PIC Files to Bitmaps
Traffic Department 2192 includes 9 PIC files, all from the first episode. All of them differ significantly in size, which will make this the biggest challenge so far. We can assume that they will basically function the same as the other graphics assets, we just have to deal with more unknowns this time.
PIC File Format
The crux of the problem underlying the PIC files are their arbitrary resolutions without header information to give us a clue. Of course, we can try all the usual suspects such as powers of 2, or multiples of the screen size. That works on just one of the files though. We have a couple of options. The first is to perform a statistical analysis focusing on autocorrelation and we'd probably happen upon the row size quickly. We don't have tools for that and it's not really in theme with this project so we'll move on the option 2. We could use the IDA Pro disassembler to see how the game binary treats the files. Specifically, find the syscall that opens the file with those names and trace the invarients of the next double-nested loop to detect the dimensions of the buffer. Sounds like a mouthful, but it's easier than option 3: Try every width by hand in the hex editor until we spot something that looks good.
The second problem we find is that some of the files have their own palette, but some rely on the default palette. This is easy to see with a hex editor, although it's visible in IDA as well. (Is TD.PAL opened along with the PIC file?)
The good news is that these are all single image assets. No need to manage sprite strip output.
The results of our investigation from IDA are as follows:
File | Dimensions | Custom Palette |
---|---|---|
SECRET.PIC | 320x32 | Yes |
TD1-13.PIC | 202x86 | Yes |
TD1-14.PIC | 200x200 | Yes |
TD1-15.PIC | 255x121 | Yes |
TD1-16.PIC | 150x170 | Yes |
TD1-17.PIC | 320x90 | No |
TD1-18.PIC | 170x104 | Yes |
TD1-19.PIC | 320x72 | No |
TD1-20.PIC | 320x100 | Yes |
Conversion Process
The process for the PIC blends the variations we've seen in the SCR, Faces, and BLK files. Set basic dimension variables based on input file name. Read in the palette either from PIC file or TD.PAL. Match each index to an RGB triple in the palette. Write the resulting set colors to the output container.
C Source Code
We'll do the work in C and pass each PIC file in as the only argument.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
/* PIC to BMP file converter designed for use with Traffic Department 2192 .PIC files * argv[1] points to .PIC file * * Five issues: * 1) This program is a security nightmare - don't compile and store * 2) No trapping, feedback, instructions, or any output whatsoever * 3) SDL_Surface normally needs to be locked due to concurrency * 4) Free the SDL surface - in this case, the OS does it * 5) Void pointer implicit cast (not portable to C++) */ #include <stdio.h> /* Opening Files */ #include <string.h> /* Manipulating File Names */ #include <stdint.h> /* Fixed-width data types (C99) */ #include <SDL.H> /* Using SDL data structure */ int main(int argc, char* argv[]) { uint8_t has_pal; /* Bool - embedded palette? */ uint16_t IMG_W, IMG_H; /* Dimensions of the image */ SDL_Surface *surface; /* SDL Surface struct */ uint8_t *dst_byte; /* Pointer to surface struct Pixels */ FILE *fin, *fpal; /* Input File and Palette pointers */ uint32_t FILE_SIZE; /* Length of File*/ uint16_t current_byte; /* Generic iterator */ uint8_t palette[768]; /* Palette array */ uint8_t src_byte; /* Single byte read from input */ uint8_t red_p, green_p, blue_p; /* Palette lookup bytes by color */ uint8_t FILENAME_LEN; /* Length of input filename */ char fout[12]; /* New filename container */ /* Get image dims based on input file name */ has_pal = 1; /* assume image has embedded palette */ if (!strcmp(argv[1],"SECRET.PIC")) { IMG_W=320; IMG_H=32; } else if (!strcmp(argv[1],"TD1-13.PIC")) { IMG_W=202; IMG_H=86; } else if (!strcmp(argv[1],"TD1-14.PIC")) { IMG_W=200; IMG_H=200; } else if (!strcmp(argv[1],"TD1-15.PIC")) { IMG_W=255; IMG_H=121; } else if (!strcmp(argv[1],"TD1-16.PIC")) { IMG_W=150; IMG_H=170; } else if (!strcmp(argv[1],"TD1-17.PIC")) { IMG_W=320; IMG_H=90; has_pal = 0;} else if (!strcmp(argv[1],"TD1-18.PIC")) { IMG_W=170; IMG_H=104; } else if (!strcmp(argv[1],"TD1-19.PIC")) { IMG_W=320; IMG_H=72; has_pal = 0;} else if (!strcmp(argv[1],"TD1-20.PIC")) { IMG_W=320; IMG_H=100; } /* Make surface and point to the pixel data */ surface = SDL_CreateRGBSurface(0, IMG_W, IMG_H, 32, 0,0,0,0); dst_byte = surface->pixels; /* Open PIC File passed as argument 1*/ fin = fopen(argv[1],"rb"); /* Get file size */ fseek(fin,0,SEEK_END); FILE_SIZE = ftell(fin); rewind(fin); /* Extract palette from PIC file (if present) or default file*/ fpal = has_pal ? fin : fopen("TD.PAL","rb"); /* set pointer to palette */ for (current_byte=0; current_byte<768; current_byte++) palette[current_byte] = fgetc(fpal) << 2; /* Reranging values 63 -> 255 */ /* Extract image data, match to palette, write to surface */ for (current_byte=0; current_byte<FILE_SIZE-(has_pal*768); current_byte++) { src_byte = fgetc(fin); /* Read Byte From File */ red_p = palette[src_byte*3]; /* Lookup Red Channel */ green_p = palette[src_byte*3+1]; /* Lookup Green Channel */ blue_p = palette[src_byte*3+2]; /* Lookup Blue Channel */ dst_byte[current_byte*4] = blue_p; /* Set Blue channel pixel */ dst_byte[current_byte*4+1] = green_p; /* Set Green channel pixel */ dst_byte[current_byte*4+2] = red_p; /* Set Red channel pixel */ dst_byte[current_byte*4+3] = 0xff; /* Set Alpha channel pixel */ } fclose(fin); if (!has_pal) /* Don't close file twice (posix would segfault ~20 years ago..) */ fclose(fpal); /* New output filename with .BMP extension */ FILENAME_LEN = strlen(argv[1]); /* Length of input file name */ strncpy(fout,argv[1],FILENAME_LEN-3); /* Chop off PIC extension */ fout[FILENAME_LEN-3]='\0'; /* Nullterm it */ strncat(fout,"BMP",3); /* Attach BMP extension */ /* Save BMP*/ SDL_SaveBMP(surface, fout); return 0; } |