/* ***************************************************************************** * 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 e32toh(N) ((big_endian) ? be32toh(N) : le32toh(N)) #define htoe32(N) ((big_endian) ? htobe32(N) : htole32(N)) #define VER htoe32(0x00000100) /* 1.00 */ /* ************************************************************************** */ /* 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, uint32_t signature, uint_fast32_t version) { DREAD(hd, casemul_internal_header) /* check type */ if (signature != hd.signature) { #if LOGLEVEL <= ll_error const char *a = (char*)&signature, *b = (char*)&hd.signature; log_error("signature mismatch!"); log_error("expected '%c%c%c%c', got '%c%c%c%c'", a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]); #endif return (g1m_error_magic); } /* check version */ if (le32toh(hd.version) != version) { log_error("version mismatch!"); log_error("epxected %08" PRIxFAST32 ", got %08" PRIx32, version, 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. * @arg big_endian big endian? * @return the error code. */ static int read_top(g1m_buffer_t *buffer, char *name, uint_fast32_t *length, int big_endian) { /* read and check the name length */ uint32_t name_length; READ(&name_length, sizeof(uint32_t)) name_length = e32toh(name_length); /* read the raw name */ uint8_t buf[name_length]; READ(buf, name_length) /* copy to the real name */ if (name_length > 8) name_length = 8; memcpy(name, buf, name_length); name[name_length] = 0; /* read the length and correct */ READ(length, sizeof(uint32_t)) *length = e32toh(*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. * @arg big_endian whether the file is big endian or not. * @return the error (if occured). */ static int read_picture(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer, int big_endian) { 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, big_endian)) || (err = read_internal(buffer, MAKELONG('PI', 'CT'), VER))) return (err); /* specific things */ DREAD(pct, casemul_pict_header) log_info("picture dimension is %d*%dpx", pct.width, pct.height); /* read all of the pixels */ unsigned int total = pct.width * pct.height; uint8_t rawpx[total]; READ(rawpx, total) uint8_t *px = rawpx; /* make the head and allocate file */ g1m_mcshead_t head = { .g1m_mcshead_type = g1m_mcstype_capture, .g1m_mcshead_width = pct.width, .g1m_mcshead_height = pct.height, }; memcpy(head.g1m_mcshead_name, name, strlen(name) + 1); head.g1m_mcshead_id = name[7] - '0'; err = g1m_make_mcsfile(pfile, &head); if (err) return (err); /* set the pixels */ uint32_t **img = (*pfile)->g1m_mcsfile_pics[0]; for (int x = 0; x < pct.width; x++) for (int y = 0; y < pct.height; y++) img[y][x] = colours[*px++]; /* finish */ 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. * @arg big_endian whether the file is big endian encoded or not. * @return the error that occured. */ static int read_matrix(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer, int big_endian) { int err; *pfile = NULL; /* general record things */ char name[13]; uint_fast32_t record_length; if ((err = read_top(buffer, name, &record_length, big_endian)) || (err = read_internal(buffer, MAKELONG('MT', 'RX'), VER))) return (err); /* read specific things */ DREAD(mtx, casemul_mtrx_header) unsigned int width = e32toh(mtx.lines) & 0x7FFF, height = e32toh(mtx.columns) & 0x7FFF; /* read double tab */ unsigned int total = width * height; double tab[total]; READ(tab, total * sizeof(double)) double *raw = tab; /* make the head and allocate file */ g1m_mcshead_t head = { .g1m_mcshead_type = g1m_mcstype_matrix, .g1m_mcshead_width = width, .g1m_mcshead_height = height, }; memcpy(head.g1m_mcshead_name, name, strlen(name) + 1); head.g1m_mcshead_id = name[4] - 'A' + 1; err = g1m_make_mcsfile(pfile, &head); if (err) return (err); /* read the matrix */ g1m_mcscell_t **cells = (*pfile)->g1m_mcsfile_cells; for (unsigned int y = 0; y < height; y++) for (unsigned int x = 0; x < width; x++) { /* read the bcd */ g1m_bcd_t bcd; g1m_bcd_fromdouble(*raw++, &bcd); #if LOGLEVEL <= ll_info /* log the bcd */ char buf[G1M_BCD_GOODBUFSIZE]; g1m_bcdtoa(&bcd, buf, G1M_BCD_GOODBUFSIZE); log_info("[%d][%d] %s", y, x, buf); #endif /* make the cell */ cells[y][x] = (g1m_mcscell_t){ .g1m_mcscell_real = bcd, .g1m_mcscell_imgn = {}, .g1m_mcscell_flags = g1m_mcscellflag_used }; } /* no error */ 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. * @arg big_endian whether the file is big endian encoded or not. * @return the error that occured. */ static int read_list(g1m_mcsfile_t **pfile, g1m_buffer_t *buffer, int big_endian) { int err; *pfile = NULL; /* general record things */ char name[13]; uint_fast32_t record_length; if ((err = read_top(buffer, name, &record_length, big_endian)) || (err = read_internal(buffer, MAKELONG('LI', 'ST'), VER))) return (err); /* read specific things */ DREAD(lst, casemul_list_header) int len = le32toh(lst.lines) & 0x7FFF; log_info("%d elements in list", len); /* read double tab */ double tab[len]; READ(tab, len * sizeof(double)) double *raw = tab; /* make head */ g1m_mcshead_t head = { .g1m_mcshead_type = g1m_mcstype_list, .g1m_mcshead_width = 1, .g1m_mcshead_height = len }; memcpy(head.g1m_mcshead_name, name, strlen(name) + 1); head.g1m_mcshead_id = name[5] - '0'; err = g1m_make_mcsfile(pfile, &head); if (err) return (err); /* read the list */ g1m_mcscell_t **cells = (*pfile)->g1m_mcsfile_cells; for (int x = 0; x < len; x++) { /* read bcd */ g1m_bcd_t bcd; g1m_bcd_fromdouble(*raw++, &bcd); #if LOGLEVEL <= ll_info /* log information */ char buf[G1M_BCD_GOODBUFSIZE]; g1m_bcdtoa(&bcd, buf, G1M_BCD_GOODBUFSIZE); log_info("[%d] %s", x, buf); #endif /* set the cell */ cells[x][0] = (g1m_mcscell_t){ .g1m_mcscell_real = bcd, .g1m_mcscell_imgn = {}, .g1m_mcscell_flags = g1m_mcscellflag_used }; } /* no error */ return (0); } /* ************************************************************************** */ /* Main function */ /* ************************************************************************** */ /** * g1m_decode_casemul: * Decode a CasEmul file. * * @arg h the handle to make. * @arg buffer the buffer to read from. * @arg big_endian whether the file is big endian or not. * @return the error code (0 if ok). */ int g1m_decode_casemul(g1m_handle_t **h, g1m_buffer_t *buffer, int big_endian) { int err; /* read the overall header's internal header end */ struct casemul_internal_header overall_int = { .signature = MAKELONG('CA', 'FS')}; READ(&overall_int.version, 8); if (e32toh(overall_int.version) != 0x100) return (g1m_error_magic); /* read the overall (global) header */ DREAD(glb, casemul_header) #if LOGLEVEL <= ll_info glb.source_offset = e32toh(glb.source_offset); glb.compiled_offset = e32toh(glb.compiled_offset); log_info("Header source offset is: 0x%08X", glb.source_offset); if (glb.flags & casemul_compiled) log_info("Header compiled offset is: 0x%08X", glb.compiled_offset); else log_info("The file has got no compiled part."); #endif /* read the source header */ if ((err = read_internal(buffer, MAKELONG('SR', 'CE'), VER))) return (err); DREAD(src, casemul_source_header) /* make the handle */ err = g1m_make_mcs(h, /* src.programs + */ src.pictures + src.matrixes + src.lists); if (err) return (err); g1m_handle_t *handle = *h; /* read each program; TODO: put this in a function when token parsing * is managed. */ for (int i = 0; i < src.programs; i++) { log_info("Reading program #%d", i + 1); log_warn("Program content will be skipped!"); /* general record things */ char name[13]; uint_fast32_t record_length; if ((err = read_top(buffer, name, &record_length, big_endian)) || (err = read_internal(buffer, MAKELONG('PR', 'OG'), 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++) { log_info("Reading picture #%d", i + 1); err = read_picture(&handle->g1m_handle_files[handle->g1m_handle_count], buffer, big_endian); if (err) goto fail; handle->g1m_handle_count++; } /* read each matrix */ for (int i = 0; i < src.matrixes; i++) { log_info("Reading matrix #%d", i + 1); err = read_matrix(&handle->g1m_handle_files[handle->g1m_handle_count], buffer, big_endian); if (err) goto fail; handle->g1m_handle_count++; } /* read each list */ for (int i = 0; i < src.lists; i++) { log_info("Reading list #%d", i + 1); err = read_list(&handle->g1m_handle_files[handle->g1m_handle_count], buffer, big_endian); if (err) goto fail; handle->g1m_handle_count++; } /* TODO: skip compiled part? */ log_warn("Should read compiled part here!"); /* no error! */ return (0); fail: g1m_free(handle); return (err); }