Skip to content

Commit

Permalink
ExFont: Properly parse the header. Until now we assumed 256 palette e…
Browse files Browse the repository at this point in the history
…ntries but this assumption did not work for Grimm's Hollow resulting in a corrupted image and heap corruption.

Also fixed how num_colors in the header is populated:
When the value is 0 the value must be the maximum allowed. Required for the ExFont logic and for our duplicate color detection logic.
  • Loading branch information
Ghabry committed Apr 18, 2021
1 parent 715bcd5 commit feed91f
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 49 deletions.
56 changes: 24 additions & 32 deletions src/exe_reader.cpp
Expand Up @@ -19,8 +19,7 @@
#ifndef EMSCRIPTEN

#include "exe_reader.h"
#include "filefinder.h"
#include "bitmap.h"
#include "image_bmp.h"
#include "output.h"
#include <iostream>
#include <fstream>
Expand Down Expand Up @@ -60,9 +59,6 @@ EXEReader::EXEReader(Filesystem_Stream::InputStream& core) : corefile(core) {
}
}

EXEReader::~EXEReader() {
}

static uint32_t djb2_hash(char* str, size_t length) {
uint32_t hash = 5381;
for (size_t i = 0; i < length; ++i) {
Expand All @@ -73,59 +69,55 @@ static uint32_t djb2_hash(char* str, size_t length) {

static std::vector<uint8_t> exe_reader_perform_exfont_save(Filesystem_Stream::InputStream& corefile, uint32_t position, uint32_t len) {
std::vector<uint8_t> exfont;
constexpr int header_size = 14;
constexpr int header_size = 14; // Size of BITMAPFILEHEADER
exfont.resize(len + header_size);

corefile.seekg(position, std::ios_base::beg);
corefile.read(reinterpret_cast<char*>(exfont.data()) + header_size, len);
if (corefile.gcount() != len) {
Output::Debug("ExFont: Error reading resource (read {}, expected {})", corefile.gcount(), len);
return {};
}

auto* exfont_data = reinterpret_cast<const uint8_t*>(exfont.data()) + header_size;
auto* e = exfont_data + len;
auto header = ImageBMP::ParseHeader(exfont_data, e);

// Solely for calculating position of actual data
uint32_t hdrL = corefile.get();
hdrL |= ((uint32_t) corefile.get()) << 8;
hdrL |= ((uint32_t) corefile.get()) << 16;
hdrL |= ((uint32_t) corefile.get()) << 24;
// As it turns out, EXFONTs appear to operate on all the same restrictions as an ordinary BMP.
// Given this particular resource is loaded by the RPG Maker half of the engine, this makes the usual amount of sense.
// This means 256 palette entries. Without fail. Even though only two are used, the first and last.
// Since this is a packed bitmap, there's nothing else to worry about.
hdrL += 256 * 4;
// Bitmap resources lack the BITMAPFILEHEADER. This header must be generated based on the BITMAPINFOHEADER.
// And the header that's going to be prepended.
hdrL += header_size;
int header_len = header_size + header.size;
if (header.depth != 8) {
Output::Debug("ExFont: Unsupported depth {}", header.depth);
return {};
}
header_len += header.num_colors * 4;

// 0 (these are in decimal)
int pos = 0;
exfont[pos++] = 'B';
exfont[pos++] = 'M';
// 2
uint32_t totallen = len + 14;
uint32_t totallen = exfont.size();
exfont[pos++] = (totallen) & 0xFF;
exfont[pos++] = (totallen >> 8) & 0xFF;
exfont[pos++] = (totallen >> 16) & 0xFF;
exfont[pos++] = (totallen >> 24) & 0xFF;
// 6
// 6 - Reserved data
exfont[pos++] = 'E';
exfont[pos++] = 'x';
exfont[pos++] = 'F';
exfont[pos++] = 'n';
// 10
exfont[pos++] = (hdrL) & 0xFF;
exfont[pos++] = (hdrL >> 8) & 0xFF;
exfont[pos++] = (hdrL >> 16) & 0xFF;
exfont[pos++] = (hdrL >> 24) & 0xFF;

corefile.seekg(position, std::ios_base::beg);
while (len > 0) {
int v = corefile.get();
if (v == -1)
break;
exfont[pos++] = v;
len--;
}
exfont[pos++] = (header_len) & 0xFF;
exfont[pos++] = (header_len >> 8) & 0xFF;
exfont[pos++] = (header_len >> 16) & 0xFF;
exfont[pos++] = (header_len >> 24) & 0xFF;

// Check if the ExFont is the original through a fast hash function
if (djb2_hash((char*)exfont.data() + header_size, exfont.size() - header_size) != 0x491e19de) {
Output::Debug("EXEReader: Custom ExFont found");
}

return exfont;
}

Expand Down
1 change: 0 additions & 1 deletion src/exe_reader.h
Expand Up @@ -36,7 +36,6 @@ class EXEReader {
// 2. max offset value is this size

EXEReader(Filesystem_Stream::InputStream& core);
~EXEReader();

// Extracts an EXFONT resource with BMP header if present
// and returns exfont buffer on success.
Expand Down
29 changes: 13 additions & 16 deletions src/image_bmp.cpp
Expand Up @@ -44,34 +44,24 @@ static uint32_t get_4(const uint8_t *&p, const uint8_t* e) {
return value;
}

namespace {
struct BitmapHeader {
int w = 0;
int h = 0;
int planes = 0;
int depth = 0;
int compression = 0;
int num_colors = 0;
int palette_size = 0;
};

BitmapHeader parseHeader(const uint8_t*& ptr, uint8_t const* const e) {
ImageBMP::BitmapHeader ImageBMP::ParseHeader(const uint8_t*& ptr, uint8_t const* const e) {
BitmapHeader hdr;

auto* hdr_start = ptr;

auto size = get_4(ptr, e);

hdr.size = size;
if (size == 12) {
//BITMAPCOREHEADER
// BITMAPCOREHEADER
hdr.w = get_2(ptr, e);
hdr.h = get_2(ptr, e);
hdr.planes = get_2(ptr, e);
hdr.depth = get_2(ptr, e);
hdr.num_colors = (1 << hdr.depth);
hdr.palette_size = 3;
} else {
//BITMAPINFOHEADER, BITMAPV4HEADER, or BITMAPV5HEADER
// BITMAPINFOHEADER, BITMAPV4HEADER, or BITMAPV5HEADER
hdr.w = get_4(ptr, e);
hdr.h = get_4(ptr, e);
hdr.planes = get_2(ptr, e);
Expand All @@ -80,12 +70,19 @@ BitmapHeader parseHeader(const uint8_t*& ptr, uint8_t const* const e) {
ptr += 12;
hdr.num_colors = std::min(uint32_t(256), get_4(ptr, e));
hdr.palette_size = 4;
if (hdr.num_colors == 0) {
// When 0 number of colors is the maximum allowed
if (hdr.depth == 4) {
hdr.num_colors = 16;
} else if (hdr.depth == 8) {
hdr.num_colors = 256;
}
}
}

ptr = hdr_start + size;
return hdr;
}
} //namespace

bool ImageBMP::ReadBMP(const uint8_t* data, unsigned len, bool transparent,
int& width, int& height, void*& pixels) {
Expand All @@ -103,7 +100,7 @@ bool ImageBMP::ReadBMP(const uint8_t* data, unsigned len, bool transparent,
ptr += BITMAPFILEHEADER_SIZE - 4;
const unsigned bits_offset = get_4(ptr, e);

auto hdr = parseHeader(ptr, e);
auto hdr = ParseHeader(ptr, e);

bool vflip = hdr.h > 0;
if (!vflip)
Expand Down
13 changes: 13 additions & 0 deletions src/image_bmp.h
Expand Up @@ -21,8 +21,21 @@
#include "filesystem_stream.h"

namespace ImageBMP {
struct BitmapHeader {
int size = 0;
int w = 0;
int h = 0;
int planes = 0;
int depth = 0;
int compression = 0;
int num_colors = 0;
int palette_size = 0;
};

bool ReadBMP(const uint8_t* data, unsigned len, bool transparent, int& width, int& height, void*& pixels);
bool ReadBMP(Filesystem_Stream::InputStream& stream, bool transparent, int& width, int& height, void*& pixels);

BitmapHeader ParseHeader(const uint8_t*& ptr, uint8_t const* e);
}

#endif

0 comments on commit feed91f

Please sign in to comment.