Hocus Pocus Sprite Format
The Hocus Pocus Sprite Format is used by Hocus Pocus to store images for game items.
The file begins with a number of sprite info blocks of the following structure:
0 UINT32LE iOffset offset to the start of this sprite's data 4 CHAR cSpriteName 26 UINT16LE iWidth4 (width in pixels = 4*iWidth4) 28 UINT16LE iHeight height in pixels 30 UINT16LE iStandFrame 32 UINT16LE iStandFrame2 maybe for animation? no effect for Hocus. 34 UINT16LE iWalkFrame1 beginning frame of walking animation 36 UINT16LE iWalkFrame2 ending frame of walking animation 38 UINT16LE iJumpFrame 40 UINT16LE iFallFrame 42 UINT16LE iShootDashFrame1 beginning frame of dashing animation/first shooting frame (Hocus' shooting up frame is always this plus one) 44 UINT16LE iShootDashFrame2 ending frame of dashing animation/second shooting frame 46 UINT16LE iProjectileWidth4 (width in pixels = 4*iProjectileWidth4) 48 UINT16LE iProjectileHeight height in pixels 50 UINT16LE iProjectileY vertical offset of projectile when fired horizontally 52 UINT16LE iProjectileFrame frame number for projectile (Hocus' upwards shot is this plus one) 54 UINT16LE iProjectileF2 no idea 56 UINT16LE iPixelsOff offset to the start of pixel data (relative to iOffset) 58 UINT16LE iPixelsSize size of pixel data 60 UINT16LE iLayoutStartsE (relative to iOffset) 100 UINT16LE iLayoutStartsW (relative to iOffset) 140 UINT16LE iPixelStartsE (relative to iPixelsOff; multiply by 4) 180 UINT16LE iPixelStartsW (relative to iPixelsOff; multiply by 4)
The number of sprite info blocks can be calculated by reading the iOffset value of the first block and dividing it by 220 (size of the sprite info block). It is also worth noting that the size of the sprite data stored at iOffset can be calculated by adding iPixelsOff and iPixelsSize. If you add that size to iOffset you get the iOffset value for the next sprite info block or the size of the entire sprite file if you have reached the last sprite info block in the file. This might be used to perform an integrity check on the sprite file.
- The iLayoutStartsE and iLayoutStartsW (E and W indicate the sprites facing East or West) might also be a single array. They are listed as seperate arrays since only the first 15 slots of each array are ever used by a sprite and one array with a bunch of unused slots in the middle seemed nonsensical. The same goes for the iPixelStart arrays.
All sprite frames appear to have the same width and height as given in the sprite info.
The number of sprite frames for the current sprite can be calculated from the Frame values. The last value in this array that is neither 0x0000 nor 0xFFFF represents the maximum frame index, and therefore, that index plus 1 is the number of frames the current sprite has.
The sprite data starting at iOffset is divided into two blocks. The first block contains layout data that is required to reconstruct the sprite images from the pixel data that forms the second block.
The layout data uses four flag values, each one byte in size:
- read transparency type (BYTE value) from layout data (see Pixel Data for more info)
- set image pointer back to 0
- read length (UINT16LE value) from layout data
- increase image pointer by length
- read 4 bytes from pixel data
- write 4 bytes to image
- increase image pointer
- increase pixel data pointer
- stop marker (this frame is done)
The image pointer is an index into the linear image buffer in which the sprite image is to be recreated. The algorithm expects an image that is exactly 320 pixels wide (and at least iHeight pixels high)! However, all pointers/indices need to be multiplied by 4 to get the actual pixel index (the sprites were probably created treating all image buffers as arrays of 32 bit integers).
During the process of recreating a sprite image, both the layout data and the pixel data need to be accessed simultaneously. Loading the pixel data of the sprite frame into a buffer prevents you from having to manage two file pointers to the same file.
This is how to create a sprite image from the sprite file:
- read sprite info block
- seek to iOffset+iPixelsOff
- read iPixelsSize bytes from the file (entire pixel data block)
- create an image buffer (320 * iHeight pixels)
- (treat both image buffer and pixel data buffer as INT32 arrays)
- initialize pixel data index to PixelStartsX[frame]
- seek to iOffset+LayoutStartsX[frame]
- read flag bytes and perform corresponding operations until flag byte is 0x03
The pixel data consists of numerous short rows, each four pixels wide. The order in which the rows are stored depends on the number and location of transparent pixels in the row and the location of the row in the original image. Rows that consist entirely of transparent pixels are NOT stored. The rows were probably created from the original image using the following scheme:
- for every row of 4 pixels:
- store a transparency type value of at least 4 bits where bit i is set if and only if pixel i is not transparent
- for v = 15 down to 1:
- for every row of 4 pixels:
- if the stored value is equal to v then write this row to the file
- for every row of 4 pixels:
This "compression" method (omitting fully transparent rows) appears to be rather useless as the layout data required to reconstruct the original image is usually larger than the amount of bytes that were saved by omitting the transparent rows.
The color indices in the pixel data should never be larger than 127 as the last 128 colors in the palette are specific to the backdrop files. The palette data from GAMEPAL.PAL should be used to display the sprite images, with color index 0 indicating a transparent pixel.
This file format was reverse engineered by K1n9_Duk3. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)