mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-10 08:18:14 +03:00
Add fnldump utility
Add tool for extracting bitmap fonts from .fnl files (font archives). This is mainly for testing the underlying .fnl reading code, which is needed for proper font rendering in the MangaGamer version of Sengoku Rance. In the future this tool could be updated to support rebuilding font archives, which would allow adding special glyphs, using condensed fonts, etc. Credit to CookieWukk for reverse engineering the .fnl file format.
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
FILE *file_open_utf8(const char *path, const char *mode);
|
||||
void *file_read(const char *path, size_t *len_out);
|
||||
|
||||
@@ -28,6 +28,8 @@ struct buffer {
|
||||
|
||||
void buffer_init(struct buffer *r, uint8_t *buf, size_t size);
|
||||
int32_t buffer_read_int32(struct buffer *r);
|
||||
uint8_t buffer_read_u8(struct buffer *r);
|
||||
uint16_t buffer_read_u16(struct buffer *r);
|
||||
float buffer_read_float(struct buffer *r);
|
||||
struct string *buffer_read_pascal_string(struct buffer *r);
|
||||
void buffer_skip(struct buffer *r, size_t off);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SYSTEM4_FNL_H
|
||||
#define SYSTEM4_FNL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct fnl {
|
||||
uint32_t filesize;
|
||||
uint32_t uk;
|
||||
uint32_t data_offset;
|
||||
uint32_t nr_fonts;
|
||||
struct fnl_font *fonts;
|
||||
};
|
||||
|
||||
struct fnl_font {
|
||||
uint32_t nr_faces;
|
||||
struct fnl_font_face *faces;
|
||||
};
|
||||
|
||||
struct fnl_font_face {
|
||||
uint32_t height;
|
||||
uint32_t uk;
|
||||
uint32_t nr_glyphs;
|
||||
struct fnl_glyph *glyphs;
|
||||
};
|
||||
|
||||
struct fnl_glyph {
|
||||
uint16_t real_width;
|
||||
uint32_t data_pos;
|
||||
uint32_t data_compsize;
|
||||
// uncompressed data
|
||||
unsigned long data_size;
|
||||
uint8_t *data;
|
||||
};
|
||||
|
||||
struct fnl *fnl_open(const char *path);
|
||||
void fnl_free(struct fnl *fnl);
|
||||
|
||||
#endif /* SYSTEM4_FNL_H */
|
||||
@@ -37,6 +37,22 @@ int32_t buffer_read_int32(struct buffer *r)
|
||||
return v;
|
||||
}
|
||||
|
||||
uint8_t buffer_read_u8(struct buffer *r)
|
||||
{
|
||||
if (r->index + 1 > r->size)
|
||||
ERROR("Out of bounds buffer read");
|
||||
return r->buf[r->index++];
|
||||
}
|
||||
|
||||
uint16_t buffer_read_u16(struct buffer *r)
|
||||
{
|
||||
if (r->index + 2 > r->size)
|
||||
ERROR("Out of bounds buffer read");
|
||||
uint16_t v = LittleEndian_getW(r->buf, r->index);
|
||||
r->index += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
float buffer_read_float(struct buffer *r)
|
||||
{
|
||||
union { int32_t i; float f; } v;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <zlib.h>
|
||||
#include "system4.h"
|
||||
#include "system4/buffer.h"
|
||||
#include "system4/fnl.h"
|
||||
#include "file.h"
|
||||
#include "little_endian.h"
|
||||
|
||||
static void fnl_read_glyph(struct buffer *r, uint32_t height, struct fnl_glyph *dst)
|
||||
{
|
||||
//dst->uk0 = buffer_read_u8(r);
|
||||
//dst->uk1 = buffer_read_u8(r);
|
||||
dst->real_width = buffer_read_u16(r);
|
||||
dst->data_pos = buffer_read_int32(r);
|
||||
dst->data_compsize = buffer_read_int32(r);
|
||||
|
||||
if (!dst->data_pos) {
|
||||
dst->data_size = 0;
|
||||
dst->data = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// uncompress...
|
||||
dst->data_size = height * height * 4; // FIXME: determine real bound
|
||||
dst->data = xmalloc(dst->data_size);
|
||||
int rv = uncompress(dst->data, &dst->data_size, r->buf + dst->data_pos, dst->data_compsize);
|
||||
if (rv != Z_OK) {
|
||||
if (rv == Z_BUF_ERROR)
|
||||
ERROR("uncompress failed: Z_BUF_ERROR");
|
||||
else if (rv == Z_MEM_ERROR)
|
||||
ERROR("uncompress failed: Z_MEM_ERROR");
|
||||
else if (rv == Z_DATA_ERROR)
|
||||
ERROR("uncompress failed: Z_DATA_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
static void fnl_read_font_face(struct buffer *r, struct fnl_font_face *dst)
|
||||
{
|
||||
dst->height = buffer_read_int32(r);
|
||||
dst->uk = buffer_read_int32(r);
|
||||
dst->nr_glyphs = buffer_read_int32(r);
|
||||
|
||||
dst->glyphs = xcalloc(dst->nr_glyphs, sizeof(struct fnl_glyph));
|
||||
for (size_t i = 0; i < dst->nr_glyphs; i++) {
|
||||
fnl_read_glyph(r, dst->height, &dst->glyphs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void fnl_read_font(struct buffer *r, struct fnl_font *dst)
|
||||
{
|
||||
dst->nr_faces = buffer_read_int32(r);
|
||||
|
||||
dst->faces = xcalloc(dst->nr_faces, sizeof(struct fnl_font_face));
|
||||
for (size_t i = 0; i < dst->nr_faces; i++) {
|
||||
fnl_read_font_face(r, &dst->faces[i]);
|
||||
}
|
||||
}
|
||||
|
||||
struct fnl *fnl_open(const char *path)
|
||||
{
|
||||
size_t filesize;
|
||||
struct fnl *fnl = xcalloc(1, sizeof(struct fnl));
|
||||
uint8_t *data = file_read(path, &filesize);
|
||||
|
||||
if (data[0] != 'F' || data[1] != 'N' || data[2] != 'A' || data[3] != '\0')
|
||||
goto err;
|
||||
|
||||
struct buffer r;
|
||||
buffer_init(&r, data, filesize);
|
||||
buffer_skip(&r, 4);
|
||||
fnl->uk = buffer_read_int32(&r);
|
||||
if (fnl->uk != 0)
|
||||
WARNING("Unexpected value for fnl->uk: %d", fnl->uk);
|
||||
|
||||
fnl->filesize = buffer_read_int32(&r);
|
||||
fnl->data_offset = buffer_read_int32(&r);
|
||||
fnl->nr_fonts = buffer_read_int32(&r);
|
||||
|
||||
fnl->fonts = xcalloc(fnl->nr_fonts, sizeof(struct fnl_font));
|
||||
for (size_t i = 0; i < fnl->nr_fonts; i++) {
|
||||
fnl_read_font(&r, &fnl->fonts[i]);
|
||||
}
|
||||
free(data);
|
||||
return fnl;
|
||||
err:
|
||||
free(fnl);
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void fnl_free(struct fnl *fnl)
|
||||
{
|
||||
for (size_t font = 0; font < fnl->nr_fonts; font++) {
|
||||
for (size_t face = 0; face < fnl->fonts[font].nr_faces; face++) {
|
||||
for (size_t glyph = 0; glyph < fnl->fonts[font].faces[face].nr_glyphs; glyph++) {
|
||||
free(fnl->fonts[font].faces[face].glyphs[glyph].data);
|
||||
}
|
||||
free(fnl->fonts[font].faces[face].glyphs);
|
||||
}
|
||||
free(fnl->fonts[font].faces);
|
||||
}
|
||||
free(fnl->fonts);
|
||||
free(fnl);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ system4 = ['afa.c',
|
||||
'cg.c',
|
||||
'ex.c',
|
||||
'file.c',
|
||||
'fnl.c',
|
||||
'instructions.c',
|
||||
'pms.c',
|
||||
'qnt.c',
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <webp/encode.h>
|
||||
#include "little_endian.h"
|
||||
#include "system4.h"
|
||||
#include "system4/fnl.h"
|
||||
#include "system4/string.h"
|
||||
#include "system4/utfsjis.h"
|
||||
#include "file.h"
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
puts("Usage: fnldump [options...] input-file");
|
||||
puts(" Unpack AliceSoft font archive files (.fnl)");
|
||||
puts("");
|
||||
puts(" -h, --help Display this message and exit");
|
||||
puts(" -o, --output Speficy the output directory");
|
||||
}
|
||||
|
||||
void write_bitmap(FILE *f, uint32_t width, uint32_t height, uint8_t *pixels)
|
||||
{
|
||||
// expand to RGB packed pixel
|
||||
uint8_t *rgb = xmalloc(width * height * 3);
|
||||
|
||||
uint32_t p = 0;
|
||||
for (int row = height - 1; row >= 0; row--) {
|
||||
for (unsigned col = 0; col < width; col++, p++) {
|
||||
uint8_t v = pixels[p/8] & (1 << (7 - p % 8)) ? 255 : 0;
|
||||
rgb[row*width*3 + col*3 + 0] = v;
|
||||
rgb[row*width*3 + col*3 + 1] = v;
|
||||
rgb[row*width*3 + col*3 + 2] = v;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *output;
|
||||
size_t len = WebPEncodeLosslessRGB(rgb, width, height, width*3, &output);
|
||||
|
||||
if (!fwrite(output, len, 1, f)) {
|
||||
ERROR("fwrite failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
free(rgb);
|
||||
WebPFree(output);
|
||||
return;
|
||||
}
|
||||
|
||||
enum {
|
||||
LOPT_HELP = 256,
|
||||
LOPT_OUTPUT,
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
char *output_dir = ".";
|
||||
while (1) {
|
||||
static struct option long_options[] = {
|
||||
{ "help", no_argument, 0, LOPT_HELP },
|
||||
{ "output", required_argument, 0, LOPT_OUTPUT },
|
||||
};
|
||||
int option_index = 0;
|
||||
int c = getopt_long(argc, argv, "ho:", long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'h':
|
||||
case LOPT_HELP:
|
||||
usage();
|
||||
return 0;
|
||||
case 'o':
|
||||
case LOPT_OUTPUT:
|
||||
output_dir = optarg;
|
||||
break;
|
||||
case '?':
|
||||
ERROR("Unrecognized command line argument");
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc != 1) {
|
||||
usage();
|
||||
ERROR("Wrong number of arguments");
|
||||
}
|
||||
|
||||
struct fnl *fnl = fnl_open(argv[0]);
|
||||
for (size_t font = 0; font < fnl->nr_fonts; font++) {
|
||||
NOTICE("FONT %zu", font);
|
||||
for (size_t face = 0; face < fnl->fonts[font].nr_faces; face++) {
|
||||
struct fnl_font_face *font_face = &fnl->fonts[font].faces[face];
|
||||
NOTICE("\tsize %lu (%lu glyphs)", font_face->height, font_face->nr_glyphs);
|
||||
|
||||
// create subdir
|
||||
sprintf(path, "%s/font_%zu/face_%zu", output_dir, font, face);
|
||||
mkdir_p(path);
|
||||
|
||||
// extract glyphs
|
||||
for (size_t g = 0; g < font_face->nr_glyphs; g++) {
|
||||
struct fnl_glyph *glyph = &font_face->glyphs[g];
|
||||
if (!glyph->data)
|
||||
continue;
|
||||
|
||||
sprintf(path, "%s/font_%zu/face_%zu/glyph_%zu.webp", output_dir, font, face, g);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
ERROR("fopen failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
uint32_t height = font_face->height;
|
||||
uint32_t width = (glyph->data_size*8) / height;
|
||||
write_bitmap(f, width, height, glyph->data);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
fnl_free(fnl);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fnldump = ['fnldump.c',
|
||||
]
|
||||
|
||||
executable('fnldump', fnldump,
|
||||
dependencies: tool_deps,
|
||||
c_args : ['-Wno-unused-parameter'],
|
||||
include_directories : incdir,
|
||||
link_with : libsys4)
|
||||
@@ -9,3 +9,4 @@ subdir('aindump')
|
||||
subdir('ainedit')
|
||||
subdir('ar')
|
||||
subdir('ex')
|
||||
subdir('fnl')
|
||||
|
||||
Reference in New Issue
Block a user