/* ***************************************************************************** * decode/cas.c -- decode a CASIOLINK 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/cas * ************************************************************************** */ #include #define FUNC(NAME) g1m_decode_caspart_##NAME #define HFUNC(NAME) g1m_decode_cashpart_##NAME #define READCOLON() { \ uint8_t colon; GREAD(&colon, 1) \ err = g1m_error_magic; \ if (colon != ':') goto fail; } /* ************************************************************************** */ /* Type correspondance list */ /* ************************************************************************** */ /* Part parsing function type */ typedef int (*cas_decode_function)(g1m_mcsfile_t*, g1m_buffer_t*); typedef int (*cas_heads_decode_function)(g1m_mcshead_t *head, g1m_mcshead_t *heads, g1m_buffer_t *buffer); /* Correspondance type */ struct cas_corresp { unsigned int type; cas_decode_function decode; cas_heads_decode_function hdecode; }; /* All correspondances */ #define TTERM {0, NULL, NULL} static struct cas_corresp cas_types[] = { {g1m_mcstype_var, FUNC(var), NULL}, {g1m_mcstype_program, FUNC(program), HFUNC(program)}, {g1m_mcstype_matrix, FUNC(matrix), NULL}, {g1m_mcstype_list, FUNC(matrix), NULL}, {g1m_mcstype_capture, FUNC(capture), NULL}, TTERM }; /** * lookup_cas_decode: * Lookup for a parsing function for the CAS part. * * @arg type the libg1m MCS file type. * @return the function (NULL if not found). */ static void *lookup_cas_decode(g1m_mcstype_t type, int heads) { /* lookup for the type */ struct cas_corresp *c = cas_types; while (c->decode) { if (type == c->type) break; c++; } /* return the function */ return (heads ? (void*)c->hdecode : (void*)c->decode); } /* ************************************************************************** */ /* Head decoding functions */ /* ************************************************************************** */ /** * decode_caspro_head: * Decode a CASIOLINK Protocol header. * * @arg head the head to fill. * @arg hd the header. * @return if there was an error, or not. */ static int decode_caspro_head(g1m_mcshead_t *head, struct caspro_header *hd) { /* log the raw header */ log_info("Raw CASPRO header:"); logm_info(hd, sizeof(struct caspro_header)); /* check the checksum */ uint8_t csum = ~g1m_checksum8(hd, sizeof(struct caspro_header) - 1) + 1; if (csum != hd->checksum) return (g1m_error_checksum); /* copy the basic information */ head->size = be16toh(hd->length) - 2 /* checksum, colon */; char *end = memchr(hd->name, 0xFF, 8); size_t len = end ? (size_t)(end - (char*)hd->name) : 8; memcpy(head->name, hd->name, len); head->name[len] = 0; /* read specific data */ switch (head->type) { case g1m_mcstype_program:; /* copy password */ head->flags |= g1m_mcsflag_unfinished; end = memchr(hd->aux, 0xFF, 8); len = end ? (size_t)(end - (char*)hd->aux) : 8; memcpy(head->password, hd->aux, len); head->password[len] = 0; log_info("Is a program of %" PRIuFAST32 " bytes", head->size); break; case g1m_mcstype_variable: if (hd->used) head->flags |= g1m_mcsflag_unfinished; /* TODO: id */ head->count = 1; break; case g1m_mcstype_matrix: head->height = hd->used; head->width = be16toh(hd->length) & 0xFF; break; case g1m_mcstype_list: head->height = hd->used; head->width = 1; break; } /* TODO */ return (0); } /** * g1m_decode_casfile_head: * Decode the CAS file head. * * @arg head the head to decode. * @arg buffer the buffer to read the header from. * @return the error code (0 if ok). */ int g1m_decode_casfile_head(g1m_mcshead_t *head, g1m_buffer_t *buffer) { /* check that the head exists */ if (!head) return (-1); memset(head, 0, sizeof(g1m_mcshead_t)); /* read beginning of the header, try to guess a newer header */ uint8_t buf[49]; READ(buf, 7) struct caspro_header *phd = (void*)buf; if (!g1m_maketype_caspro(head, (char*)phd->type, (char*)phd->data)) { READ(&buf[7], 42) return (decode_caspro_head(head, phd)); } /* check the data type */ READ(&buf[7], 32) struct cas_header *hd = (void*)buf; log_info("Raw CAS header:"); logm_info(hd, sizeof(struct cas_header)); if (g1m_maketype_cas(head, (char*)hd->data)) return (g1m_error_unrecognized); /* fill the handle */ memcpy(head->name, hd->filename, 12); head->name[12] = 0; /* type specific */ if (head->type == g1m_mcstype_program) { struct cas_spe_program *spe = (void*)hd->misc; head->size = be16toh(spe->length); /* TODO: store flags? */ } /* no error! */ return (0); } /* ************************************************************************** */ /* Part decoding functions */ /* ************************************************************************** */ /** * g1m_decode_casfiles_part: * Decode the CASIOLINK group parts. * * @arg head the general head. * @arg heads the heads to contribute to. * @arg buffer the buffer to read from. * @return the error that occurred (0 if ok). */ int g1m_decode_casfiles_part(g1m_mcshead_t *head, g1m_mcshead_t *heads, g1m_buffer_t *buffer) { /* look for the decoding function */ cas_heads_decode_function decode = (void*)lookup_cas_decode(head->type, 1); if (!decode) { log_error("No dedicated decoding function was found for this type!"); return (g1m_error_unknown); } /* decode the part */ return ((*decode)(head, heads, buffer)); } /** * g1m_decode_casfile_part: * Decode a CASIOLINK Protocol content part. * * @arg file the file to contribute to. * @arg buffer the buffer to read from. * @return if there was an error, or not. */ int g1m_decode_casfile_part(g1m_mcsfile_t *file, g1m_buffer_t *buffer) { /* checks */ if (!file) return (g1m_error_op); /* look for the decoding function */ cas_decode_function decode = (void*)lookup_cas_decode(file->head.type, 0); if (!decode) { log_error("No dedicated decoding function was found for this type!"); return (g1m_error_unknown); } /* decode the part */ return ((*decode)(file, buffer)); } /* ************************************************************************** */ /* File decoding function */ /* ************************************************************************** */ /** * g1m_decode_cas: * Decode a CAS file. TODO. * * Is also sort of a guide for using the CAS MCS files. * The colon ':' (0x3A) is already read at this point (CAS file id.). * * @arg handle the handle to create. * @arg buffer the buffer to read from. * @return the libg1m error. */ int g1m_decode_cas(g1m_t *handle, g1m_buffer_t *buffer) { int err; /* prepare the handle */ handle->type = g1m_type_mcs; handle->files = NULL; handle->_size = 0; /* read each */ for (handle->count = 0;;) { /* read the head */ log_info("Reading the next head."); g1m_mcshead_t head = {}; if ((err = g1m_decode_casfile_head(&head, buffer))) goto fail; if (head.type == g1m_mcstype_end) break; /* heads up! */ int numheads = head.flags & g1m_mcsflag_multiple ? head.count : 1; g1m_mcshead_t heads[numheads]; if (head.flags & g1m_mcsflag_multiple) { log_info("Is a head for multiple files!"); g1m_prepare_mcsfile_heads(&head, heads); while (head.flags & g1m_mcsflag_unfinished) { READCOLON() /* decode a general part */ err = g1m_decode_casfiles_part(&head, heads, buffer); if (err) goto fail; } } else heads[0] = head; for (int i = 0; i < numheads; i++) { /* prepare the file */ log_info("Preparing the group file #%d", i); g1m_mcsfile_t *file; err = g1m_mcs_insert(handle, &heads[i], &file); if (err) goto fail; /* read each part */ #if LOGLEVEL <= ll_info for (int j = 1; file->head.flags & g1m_mcsflag_unfinished; j++) { #else while (file->head.flags & g1m_mcsflag_unfinished) { #endif /* initialize */ log_info("Reading part #%d", j); READCOLON() /* read the part */ err = g1m_decode_casfile_part(file, buffer); if (err) goto fail; } } /* read first colon of the next part */ READCOLON() } /* everything went well :) */ return (0); fail: /* END OF DA WORLD */ g1m_free_mcs(handle); return (err); } /** * g1m_decode_grc: * Decode Graph Card file. * * @arg handle the handle. * @arg buffer the buffer to read from. * @return the error code (0 if ok). */ int g1m_decode_grc(g1m_t *handle, g1m_buffer_t *buffer) { uint8_t intro[2]; READ(intro, 2) /* probably the file count? */ uint8_t colon; READ(&colon, 1) /* read first colon */ return (g1m_decode_cas(handle, buffer)); }