QFN Format (Blood)

From ModdingWiki
Jump to: navigation, search

The QFN format is used by Blood below version 1.21 to store fonts. These font files located inside two game archives in RFF format. Newer versions of the game uses font tiles from TILES016.ART and TILES017.ART instead.

  • GUI.RFF (looks like these fonts are unused):
  1. QFONT1.FNT (8x8, mono, 1BPP)
  2. QFONT2.FNT (8x9, mono, 1BPP)
  • BLOOD.RFF (these fonts are removed from the archive in the newer versions of the game):
  1. FONTBLOD.QFN (12x14, color, 8BPP)
  2. FONTSMAL.QFN (5x7, color, 8BPP)
  3. KFONT6.QFN (16x15, color, 8BPP)
  4. KFONT7.QFN (6x8, color, 8BPP)
  5. PFONT2O.QFN (11x11, color, 8BPP)

Note that all structures and names below restored from the grabfont.exe tool debug symbols from the leaked Blood Alpha code. So there are names and sizes (in bytes) but no information about type signs (signed or unsigned) except for a few places like voffset where unsigned values doesn't make a sense because it was obviously too big for any sane usage.

Like most of the Blood file formats there are a lot of unused data like startChar and endChar - both are usually 0 in mentioned font files and info character table hold records for all 256 characters anyway (actual font image data stored only for existing font characters).

QFONT font header

Data type Name Description
char[4] signature File signature, always "FNT\x1A"
UINT16LE version Format version, always 0x100
UINT16LE type Font type: 0 - mono font (1BPP), else - color font (8BPP)
UINT32LE totalsize Font character data size
UINT8 startChar ! Unknown First character in font (unused, usually 0)
UINT8 endChar ! Unknown Last character in font (unused, usually 0)
UINT8 Blending ! Unknown Blending (unused, 0)
INT8 baseline Font baseline
UINT8 tcolor ! Unknown Transparent color (unused, 0xFF)
INT8 charSpace Horizonal space between characters
UINT8 width Max character width
UINT8 height Max character height
char[12] fill Padding for this structure up to 32 bytes
CHARINFO[256] info Characters info

CHARINFO character info

Data type Name Description
UINT32LE offset Character data offset (relative to end of the QFONT header)
UINT8 cols Character width
UINT8 rows Character height
INT8 hspace Character horizontal size
INT8 voffset Character vertical offset from top

Source Code

C

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <inttypes.h>
 
/*
  (c) CTPAX-X Team 2017
  http://www.CTPAX-X.org/
 
  Helping a tiny bit with the BloodGDX project:
  http://m210.duke4.net/index.php/files/viewdownload/9-java/50-bloodgdx
 
  Example source code to show how to work with the font files.
  Outputs specified input string with the selected font file to the text screen.
 
  Also for the references see the code of viewDrawChar() and viewDrawText()
  in VIEW.CPP file from the leaked Blood Alpha source codes.
*/
 
/*
  Structures below restored from the "grabfont.exe" debug symbols,
  so not always sure about signed/unsigned types where not specified.
*/
 
#pragma pack(push, 1)
/* 8 bytes each record */
typedef struct {
  uint32_t offset; /* image data offset (relative to the end of the header) */
  uint8_t cols;    /* image data width */
  uint8_t rows;    /* image data height */
  int8_t hspace;   /* horizontal space (signed) */
  int8_t voffset;  /* vertical offset (signed) */
} CHARINFO;
 
/* 2080 bytes this header */
typedef struct {
  char signature[4];  /* always "FNT\x1A" */
  uint16_t version;   /* always 0x100 */
  uint16_t type;      /* 0 - monochrome (1 BPP); everything else - color (8BPP) */
  uint32_t totalsize; /* total size of font data (size of this header + totalsize = filesize */
  uint8_t startChar;  /* unused */
  uint8_t endChar;    /* unused */
  uint8_t Blending;   /* unused */
  int8_t baseline;    /* character baseline (signed) */
  uint8_t tcolor;     /* unused - transparent color - looks like always 0xFF */
  int8_t charSpace;   /* character space (signed) */
  uint8_t width;      /* max character width */
  uint8_t height;     /* max character height */
  char fill[12];      /* filler to 32 bytes boundary */
  CHARINFO info[256]; /* characters info */
  uint8_t data[0];    /* image data */
} QFONT;
#pragma pack(pop)
 
/* virtual screen size */
#define SCR_W 80
#define SCR_H 50
 
int main(int argc, char *argv[]) {
FILE *fl;
uint8_t *pFont;
int sz, x, y, i, j;
char *s;
QFONT *qf;
CHARINFO *ci;
/* virtual screen */
char scr[SCR_W*SCR_H];
  /* Example: qfn_show FONT1.QFN "Blood Font!" > fontshow.txt */
  if (argc != 3) {
    printf("Usage: qfn_show <filename.qfn> <string>\n\n");
    return(1);
  }
  fl = fopen(argv[1], "rb");
  if (!fl) {
    printf("Error opening input file for read!\n\n");
    return(2);
  }
  fseek(fl, 0, SEEK_END);
  sz = ftell(fl);
  fseek(fl, 0, SEEK_SET);
  pFont = (uint8_t *) malloc(sz);
  if (!pFont) {
    fclose(fl);
    printf("Error memory allocation!\n\n");
    return(3);
  }
  fread(pFont, sz, 1, fl);
  fclose(fl);
  qf = (QFONT *) pFont;
  /* check header */
  if (
     /* signature */
     (memcmp(qf->signature, "FNT\x1A", 4)) ||
     /* only one existing version */
     (qf->version != 0x100) ||
     /* whole file size: header + font data */
     (sz != (sizeof(qf[0]) + qf->totalsize))
  ) {
    free(pFont);
    printf("Error invalid font file!\n\n");
    return(4);
  }
  /* string to output */
  s = argv[2];
  /* empty virtual screen */
  memset(scr, ' ', SCR_W*SCR_H);
  /* output text string */
  x = 0;
  for (; *s; s++) {
    /* get current character */
    ci = &qf->info[(uint8_t) *s];
    /* screen y pos */
    y = qf->baseline + ci->voffset;
    /* screen overflow? */
    if (x + ci->hspace >= SCR_W) { break; }
    /* for each pixel */
    for (j = 0; j < ci->rows; j++) {
      /* screen overflow? */
      if (j + y >= SCR_H) { break; }
      for (i = 0; i < ci->cols; i++) {
        if (qf->type == 0) {
          /* mono */
          if (qf->data[ci->offset + i + ((j/8)*ci->cols)] & (1 << (j%8))) {
            scr[((j + y) * SCR_W) + x + i] = '#';
          }
        } else {
          /* color */
          if (qf->data[ci->offset + (i*ci->rows) + j] != 0xFF) {
            /* NOTE: this will breaks console output for codes < 32 but this a test tool */
            scr[((j + y) * SCR_W) + x + i] = qf->data[ci->offset + (i*ci->rows) + j];
          }
        }
      }
    }
    /* add char horizonal size + generic space between characters */
    x += ci->hspace + qf->charSpace;
  }
  /* min height for output (reduce empty lines) */
  y = (SCR_H < qf->height) ? SCR_H : qf->height;
  /* output virtual screen */
  for (j = 0; j < y; j++) {
    /* find line end */
    x = 0;
    /* -1 or there will be 2 linebreaks at 80 */
    for (i = 0; i < SCR_W - 1; i++) {
      if (scr[(j*SCR_W) + i] != ' ') {
        x = i + 1;
      }
    }
    i = (j*SCR_W);
    /* set line end */
    scr[i + x] = 0;
    /* output line */
    printf("%s\n", &scr[i]);
  }
  /* show some font info */
  printf(
    "%s\n%dx%d %s\ncharSpace: %d\nbaseline: %d\n\n",
    argv[1], qf->width, qf->height, qf->type ? "color" : "mono", qf->charSpace, qf->baseline
  );
  free(pFont);
  return(0);
}

Credits

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