diff --git a/include/file.h b/include/file.h index 2508399..ba212f4 100644 --- a/include/file.h +++ b/include/file.h @@ -19,6 +19,7 @@ #include #include +#include FILE *file_open_utf8(const char *path, const char *mode); void *file_read(const char *path, size_t *len_out); diff --git a/include/system4/buffer.h b/include/system4/buffer.h index 6ccc57f..9669b1d 100644 --- a/include/system4/buffer.h +++ b/include/system4/buffer.h @@ -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); diff --git a/include/system4/fnl.h b/include/system4/fnl.h new file mode 100644 index 0000000..b7d2be6 --- /dev/null +++ b/include/system4/fnl.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 . + */ + +#ifndef SYSTEM4_FNL_H +#define SYSTEM4_FNL_H + +#include + +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 */ diff --git a/src/buffer.c b/src/buffer.c index 6a58998..bcd32f0 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -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; diff --git a/src/fnl.c b/src/fnl.c new file mode 100644 index 0000000..9656c55 --- /dev/null +++ b/src/fnl.c @@ -0,0 +1,121 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 . + */ + +#include +#include +#include +#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); +} diff --git a/src/meson.build b/src/meson.build index 14839c3..faf2bda 100644 --- a/src/meson.build +++ b/src/meson.build @@ -8,6 +8,7 @@ system4 = ['afa.c', 'cg.c', 'ex.c', 'file.c', + 'fnl.c', 'instructions.c', 'pms.c', 'qnt.c', diff --git a/src/tools/fnl/fnldump.c b/src/tools/fnl/fnldump.c new file mode 100644 index 0000000..4bd25de --- /dev/null +++ b/src/tools/fnl/fnldump.c @@ -0,0 +1,141 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/src/tools/fnl/meson.build b/src/tools/fnl/meson.build new file mode 100644 index 0000000..ff7080d --- /dev/null +++ b/src/tools/fnl/meson.build @@ -0,0 +1,8 @@ +fnldump = ['fnldump.c', +] + +executable('fnldump', fnldump, + dependencies: tool_deps, + c_args : ['-Wno-unused-parameter'], + include_directories : incdir, + link_with : libsys4) diff --git a/src/tools/meson.build b/src/tools/meson.build index 6daceaf..c849380 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -9,3 +9,4 @@ subdir('aindump') subdir('ainedit') subdir('ar') subdir('ex') +subdir('fnl')