diff --git a/include/libg1m/format/casemul.h b/include/libg1m/format/casemul.h index fb54a55..aeb0e62 100644 --- a/include/libg1m/format/casemul.h +++ b/include/libg1m/format/casemul.h @@ -129,6 +129,9 @@ struct casemul_comp_header { * uint32_t length; * The subheader+data length. * + * Actually, it looks like the name length is always 12 and finishes with + * "W:g\xA7"; I don't know why, I'll have to investiguate on this. XXX + * * Then expect an internal header, part of the subheader, that expresses the * type of it (expected version for all of them is 1.00). */ /* ************************************************************************** */ @@ -150,7 +153,7 @@ struct casemul_prog_header { struct casemul_pict_header { /* size */ - uint8_t w, h; + uint8_t width, height; /* ... aaaaand alignment. (you know, unchecked theory?) */ uint8_t _align[2]; diff --git a/include/libg1m/internals.h b/include/libg1m/internals.h index 5aa610c..19c8bf8 100644 --- a/include/libg1m/internals.h +++ b/include/libg1m/internals.h @@ -130,6 +130,7 @@ int g1m_decode_std(g1m_t *handle, const char *path, g1m_buffer_t *buffer, struct standard_header*, g1m_type_t expected_types); int g1m_decode_cas(g1m_t *handle, g1m_buffer_t *buffer, g1m_type_t expected_types); +int g1m_decode_casemul(g1m_t *handle, g1m_buffer_t *buffer); /* ************************************************************************** */ /* "Std"-specific decoding functions */ diff --git a/include/libg1m/internals/endian.h b/include/libg1m/internals/endian.h index 37f76e3..38ec1e5 100644 --- a/include/libg1m/internals/endian.h +++ b/include/libg1m/internals/endian.h @@ -32,12 +32,18 @@ # include # define be16toh(x) OSSwapBigToHostInt16(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) # define be32toh(x) OSSwapBigToHostInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) # define be64toh(x) OSSwapBigToHostInt64(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) # define htobe16(x) OSSwapHostToBigInt16(x) +# define htole16(x) OSSwapHostToLittleInt16(x) # define htobe32(x) OSSwapHostToBigInt32(x) +# define htole32(x) OSSwapHostToLittleInt32(x) # define htobe64(x) OSSwapHostToBigInt64(x) +# define htole64(x) OSSwapHostToLittleInt64(x) # elif defined(__OpenBSD__) # include @@ -48,20 +54,32 @@ # if BYTE_ORDER == LITTLE_ENDIAN # define be16toh(x) ntohs(x) +# define le16toh(x) (x) # define be32toh(x) ntohl(x) +# define le32toh(x) (x) # define be64toh(x) ntohll(x) +# define le64toh(x) (x) # define htobe16(x) htons(x) +# define htole16(x) (x) # define htobe32(x) htonl(x) +# define htole32(x) (x) # define htobe64(x) htonll(x) +# define htole64(x) (x) # else # define be16toh(x) (x) +# define le16toh(x) ntohs(x) # define be32toh(x) (x) +# define le32toh(x) ntohl(x) # define be64toh(x) (x) +# define le64toh(x) ntohll(x) # define htobe16(x) (x) +# define htole16(x) htons(x) # define htobe32(x) (x) +# define htole32(x) htonl(x) # define htobe64(x) (x) +# define htole64(x) htonll(x) # endif # else diff --git a/include/libg1m/mcs.h b/include/libg1m/mcs.h index 23436cc..12ac42f 100644 --- a/include/libg1m/mcs.h +++ b/include/libg1m/mcs.h @@ -29,23 +29,25 @@ /* MCS file type */ typedef unsigned int g1m_mcsfile_type_t; typedef unsigned int g1m_mcstype_t; -# define g1m_mcstype_unknown 0x0000 -# define g1m_mcstype_program 0x0001 -# define g1m_mcstype_list 0x0002 -# define g1m_mcstype_mat 0x0004 -# define g1m_mcstype_pict 0x0008 -# define g1m_mcstype_capt 0x0010 -# define g1m_mcstype_spreadsheet 0x0020 -# define g1m_mcstype_string 0x0040 -# define g1m_mcstype_setup 0x0080 -# define g1m_mcstype_alphamem 0x0100 -# define g1m_mcstype_vct 0x0200 -# define g1m_mcstype_variable 0x0400 +# define g1m_mcstype_unknown 0x0000 +# define g1m_mcstype_program 0x0001 +# define g1m_mcstype_list 0x0002 +# define g1m_mcstype_mat 0x0004 +# define g1m_mcstype_pict 0x0008 +# define g1m_mcstype_capt 0x0010 +# define g1m_mcstype_ssheet 0x0020 +# define g1m_mcstype_string 0x0040 +# define g1m_mcstype_setup 0x0080 +# define g1m_mcstype_alphamem 0x0100 +# define g1m_mcstype_vct 0x0200 +# define g1m_mcstype_variable 0x0400 /* MCS file type aliases */ -# define g1m_mcstype_picture g1m_mcstype_pict -# define g1m_mcstype_capture g1m_mcstype_capt -# define g1m_mcstype_vector g1m_mcstype_vct +# define g1m_mcstype_matrix g1m_mcstype_mat +# define g1m_mcstype_picture g1m_mcstype_pict +# define g1m_mcstype_capture g1m_mcstype_capt +# define g1m_mcstype_spreadsheet g1m_mcstype_ssheet +# define g1m_mcstype_vector g1m_mcstype_vct /* Macros to check if the type uses the ID, and to interact with it */ # define g1m_mcstype_uses_id(T) ((T) & (\ diff --git a/src/bcd/double.c b/src/bcd/double.c index bbbdeaa..e651889 100644 --- a/src/bcd/double.c +++ b/src/bcd/double.c @@ -42,7 +42,7 @@ void g1m_bcd_fromdouble(double dbl, g1m_bcd_t *bcd) exp++; dbl /= 10; } - while (dbl < 1) { + if (dbl > 0) while (dbl < 1) { exp--; dbl *= 10; } diff --git a/src/core/decode.c b/src/core/decode.c index b9d5346..d65642f 100644 --- a/src/core/decode.c +++ b/src/core/decode.c @@ -43,8 +43,15 @@ int g1m_decode(g1m_t *handle, const char *path, g1m_buffer_t *buffer, if (buf[0] == ':') return (g1m_decode_cas(handle, buffer, expected_types)); + /* identify a Casemul file */ + READ(&buf[1], 3) + log_info("Buffer content is:"); + logm_info(buf, 4); + if (!memcmp(buf, "ACFS", 4)) + return (g1m_decode_casemul(handle, buffer)); + /* identify a standard header (send a _copy_) */ - READ(&buf[1], 0x1F) + READ(&buf[1], 0x1C) uint8_t altbuf[0x20]; memcpy(altbuf, buf, 0x20); return (g1m_decode_std(handle, path, buffer, (struct standard_header*)altbuf, expected_types)); diff --git a/src/decode/casemul.c b/src/decode/casemul.c new file mode 100644 index 0000000..016d237 --- /dev/null +++ b/src/decode/casemul.c @@ -0,0 +1,378 @@ +/* ***************************************************************************** + * decode/casemul.c -- decode a Casemul file. + * Copyright (C) 2017 Thomas "Cakeisalie5" Touhey + * + * This file is part of libg1m. + * libg1m is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3.0 of the License, + * or (at your option) any later version. + * + * libg1m 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libg1m; if not, see . + * + * Based on the Casetta project documentation: + * https://casetta.tuxfamily.org/formats/casemul + * + * The support for the subfiles (program, picture, matrix, list) are not added + * in the specific files in `decode/mcs/` as this is *not* the format they + * have while in the MCS, but the format CasEmul uses internally. + * ************************************************************************** */ +#include +#define VER 0x00000100 /* 1.00, the expected version in the internal headers */ + +/* ************************************************************************** */ +/* Utilities */ +/* ************************************************************************** */ +/** + * read_internal: + * Read the internal headers, and check them. + * + * @arg buffer the buffer to read from. + * @arg type the expected type. + * @arg version the expected version. + * @return the error (if any). + */ + +static int read_internal(g1m_buffer_t *buffer, const char *type, + uint_fast32_t version) +{ + DREAD(hd, casemul_internal_header) + + /* check type */ + if (type[0] != hd.type[1] || type[1] != hd.type[0] + || type[2] != hd.type[3] || type[3] != hd.type[2]) { + log_error("type mismatch!"); + log_error("expected '%c%c%c%c', got '%c%c%c%c'", + type[0], type[1], type[2], type[3], + hd.type[1], hd.type[0], hd.type[3], hd.type[2]); + return (g1m_error_magic); + } + + /* check version */ + if (le32toh(hd.version) != version) { + log_error("version mismatch!"); + log_error("version mismatch!"); + log_error("epxected %08" PRIxFAST32 ", got %08" PRIx32, + version, le32toh(hd.version)); + return (g1m_error_magic); + } + + /* nothing wrong :) */ + return (0); +} + +/** + * read_top: + * Read the top of a record. + * + * @arg buffer the buffer to read from. + * @arg name the name buffer (13 bytes including '\0'). + * @arg length the subheader+data length. + * @return the error code. + */ + +static int read_top(g1m_buffer_t *buffer, char *name, uint_fast32_t *length) +{ + /* read and check the name length */ + uint32_t name_length; READ(&name_length, sizeof(uint32_t)) + name_length = le32toh(name_length); + + /* read the name and copy */ + uint8_t buf[name_length]; + READ(buf, name_length) + name_length = max(name_length, 8); + memcpy(name, buf, name_length); + name[name_length] = 0; + logm_info(name, name_length); + + /* read the length and correct */ + READ(length, sizeof(uint32_t)) + *length = le32toh(*length); + + /* no error! */ + return (0); +} + +/* ************************************************************************** */ +/* Intermediate functions */ +/* ************************************************************************** */ +/** + * read_picture: + * Read a picture record and content. + * + * @arg pfile the pointer to the mcsfile to make and fill. + * @arg buffer the buffer to read from. + * @return the error (if occured). + */ + +static int read_picture(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer) +{ + int err; *pfile = NULL; + static const uint32_t colours[256] = { + [0x00] = 0xFFFFFF, /* white */ + [0x01] = 0xFF8000, /* orange */ + [0x02] = 0x00FF00, /* green */ + [0x03] = 0x0000FF, /* blue */ + /* other colours are black */ + }; + + /* general record things */ + char name[13]; uint_fast32_t record_length; + if ((err = read_top(buffer, name, &record_length)) + || (err = read_internal(buffer, "PICT", VER))) + return (err); + + /* specific things */ + DREAD(pct, casemul_pict_header) + + /* read all of the pixels */ + unsigned int total = pct.width * pct.height; + uint8_t rawpx[total]; READ(rawpx, total) + uint8_t *px = rawpx; + + /* allocate the file */ + g1m_mcsfile_t *file = malloc(sizeof(g1m_mcsfile_t)); + if (!file) return (g1m_error_alloc); + memset(file, 0, sizeof(g1m_mcsfile_t)); + + /* make the pixels */ + int width = pct.width, height = pct.height; + uint32_t **img = alloc_pixels(width, height); + if (!img) { free(file); return (g1m_error_alloc); } + + /* set the head */ + file->head.type = g1m_mcstype_capt; + memcpy(file->head.name, name, strlen(name) + 1); + /* TODO: id */ + + /* set the pixels */ + prepare_pixels(img, width, height) + file->width = width; + file->height = height; + file->image = img; + for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) + img[y][x] = colours[*px++]; + + /* finish */ + *pfile = file; + return (0); +} + +/** + * read_matrix: + * Read a matrix record and content. + * + * @arg pfile the pointer to the mcsfile to make and fill. + * @arg buffer the buffer to read from. + * @return the error that occured. + */ + +static int read_matrix(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer) +{ + int err; + + /* general record things */ + char name[13]; uint_fast32_t record_length; + if ((err = read_top(buffer, name, &record_length)) + || (err = read_internal(buffer, "MTRX", VER))) + return (err); + + /* read specific things */ + DREAD(mtx, casemul_mtrx_header) + + /* read double tab */ + int width = le32toh(mtx.lines) & 0x7FFF, + height = le32toh(mtx.columns) & 0x7FFF; + unsigned int total = width * height; + double tab[total]; READ(tab, total * sizeof(double)) + double *raw = tab; + + /* allocate handle */ + g1m_mcsfile_t *file = malloc(sizeof(g1m_mcsfile_t)); + if (!file) return (g1m_error_alloc); + memset(file, 0, sizeof(g1m_mcsfile_t)); + + /* allocate matrix */ + file->cells = malloc(sizeof(g1m_mcs_cell_t*) * height); + if (!file->cells) { free(file); return (g1m_error_alloc); } + g1m_mcs_cell_t *rws = malloc(sizeof(g1m_mcs_cell_t) * height * width); + if (!rws) { free(file); free(file->cells); return (g1m_error_alloc); } + + /* set the information */ + file->head.type = g1m_mcstype_mat; + memcpy(file->head.name, name, strlen(name) + 1); + /* TODO: id */ + + /* read the matrix */ + file->columns = width; + file->rows = height; + for (int y = 0; y < height; y++) { + file->cells[y] = &rws[width * y]; + for (int x = 0; x < width; x++) { + g1m_bcd_t bcd; g1m_bcd_fromdouble(*raw++, &bcd); + file->cells[y][x] = (g1m_mcs_cell_t){ + .real = bcd, + .imgn = {}, + .used = 1 + }; + } + } + + /* no error */ + *pfile = file; + return (0); +} + +/** + * read_list: + * Read a list record and content. + * + * @arg pfile the pointer to the mcsfile to make and fill. + * @arg buffer the buffer to read from. + * @return the error that occured. + */ + +static int read_list(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer) +{ + int err; + + /* general record things */ + char name[13]; uint_fast32_t record_length; + if ((err = read_top(buffer, name, &record_length)) + || (err = read_internal(buffer, "LIST", VER))) + return (err); + + /* read specific things */ + DREAD(lst, casemul_list_header) + + /* read double tab */ + int len = le32toh(lst.lines) & 0x7FFF; + double tab[len]; READ(tab, len * sizeof(double)) + double *raw = tab; + + /* allocate handle */ + g1m_mcsfile_t *file = malloc(sizeof(g1m_mcsfile_t)); + if (!file) return (g1m_error_alloc); + memset(file, 0, sizeof(g1m_mcsfile_t)); + + /* allocate matrix */ + file->cells = malloc(sizeof(g1m_mcs_cell_t*) * len); + if (!file->cells) { free(file); return (g1m_error_alloc); } + g1m_mcs_cell_t *rws = malloc(sizeof(g1m_mcs_cell_t) * len); + if (!rws) { free(file->cells); free(file); return (g1m_error_alloc); } + + /* set the information */ + file->head.type = g1m_mcstype_list; + memcpy(file->head.name, name, strlen(name) + 1); + /* TODO: id */ + + /* read the list */ + file->columns = 1; file->rows = len; + for (int x = 0; x < len; x++) { + file->cells[x] = &rws[x]; + g1m_bcd_t bcd; g1m_bcd_fromdouble(*raw++, &bcd); + file->cells[x][0] = (g1m_mcs_cell_t){ + .real = bcd, + .imgn = {}, + .used = 1 + }; + } + + /* no error */ + *pfile = file; + return (0); +} + +/* ************************************************************************** */ +/* Main function */ +/* ************************************************************************** */ +/** + * g1m_decode_casemul: + * Decode a CasEmul file. + * + * @arg handle the handle. + * @arg buffer the buffer to read from. + * @return the error code (0 if ok). + */ + +int g1m_decode_casemul(g1m_t *handle, g1m_buffer_t *buffer) +{ + int err; + + /* read the overall header's internal header end */ + struct casemul_internal_header overall_int = {.type = "ACFS"}; + READ(&overall_int.version, 8); + if (le32toh(overall_int.version) != 0x100) + return (g1m_error_magic); + + /* read the overall (global) header */ + DREAD(glb, casemul_header) + + /* read the source header */ + if ((err = read_internal(buffer, "SRCE", VER))) + return (err); + DREAD(src, casemul_source_header) + log_info("%d program(s), %d pic(s), %d matrix(es), %d list(s)", + src.programs, src.pictures, src.matrixes, src.lists); + + /* prepare the tab */ + handle->type = g1m_type_mcs; + handle->platform = g1m_platform_fx; + handle->count = 0; + handle->_size = /* src.programs + */ src.pictures + src.matrixes + + src.lists; + if (!(handle->files = malloc(sizeof(g1m_mcsfile_t*) * handle->_size))) + return (g1m_error_alloc); + + /* read each program; TODO: put this in a function when token parsing + * is managed. */ + for (int i = 0; i < src.programs; i++) { + /* general record things */ + char name[13]; uint_fast32_t record_length; + if ((err = read_top(buffer, name, &record_length)) + || (err = read_internal(buffer, "PROG", VER))) + goto fail; + + /* specific things */ + GDREAD(prg, casemul_prog_header) + prg.length = le32toh(prg.length); + + /* TODO: decode using tokens (cf. refc and libfc) */ + SKIP(prg.length) + } + + /* read each picture */ + for (int i = 0; i < src.pictures; i++) { + if ((err = read_picture(&handle->files[handle->count], buffer))) + goto fail; + handle->count++; + } + + /* read each matrix */ + for (int i = 0; i < src.matrixes; i++) { + if ((err = read_matrix(&handle->files[handle->count], buffer))) + goto fail; + handle->count++; + } + + /* read each list */ + for (int i = 0; i < src.lists; i++) { + if ((err = read_list(&handle->files[handle->count], buffer))) + goto fail; + handle->count++; + } + + /* TODO: skip compiled part? */ + + /* no error! */ + return (0); +fail: + g1m_free_mcs(handle); + return (err); +} diff --git a/src/decode/casemul.c.draft b/src/decode/casemul.c.draft deleted file mode 100644 index 571fa07..0000000 --- a/src/decode/casemul.c.draft +++ /dev/null @@ -1,24 +0,0 @@ -/* ***************************************************************************** - * decode/casemul.c -- decode a Casemul file. - * Copyright (C) 2017 Thomas "Cakeisalie5" Touhey - * - * This file is part of libg1m. - * libg1m is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 3.0 of the License, - * or (at your option) any later version. - * - * libg1m 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with libg1m; if not, see . - * - * Based on the Casetta project documentation: - * https://casetta.tuxfamily.org/formats/casemul - * ************************************************************************** */ -#include - -/* TODO */