/* ***************************************************************************** * 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 /* ************************************************************************** */ /* 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), NULL}, {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_cas50: * Decode a CASIOLINK Protocol header. * * @arg head the head to fill. * @arg buffer the buffer to read from. * @arg csum the current checksum. * @return if there was an error, or not. */ static int decode_cas50(g1m_mcshead_t *head, g1m_buffer_t *buffer, uint8_t csum) { /* read the raw header */ DREAD(hd, _cas50) log_info("Raw CAS50 (CASPRO) header content (app: '%.3s'):", head->g1m_mcshead_appname); logm_info(&hd, sizeof(struct _cas50)); /* check the checksum */ csum += g1m_checksum8(&hd, sizeof(struct _cas50) - 1); csum = ~csum + 1; if (csum != hd.checksum) { log_error("Checksum mismatch: expected 0x%02X, got 0x%02X", hd.checksum, csum); return (g1m_error_checksum); } /* copy the basic information */ g1m_maketype_cas(head, (char*)hd.data); head->g1m_mcshead_size = be16toh(hd.height) - 2 /* checksum, colon */; char *end = memchr(hd.name, 0xFF, 8); size_t len = end ? (size_t)(end - (char*)hd.name) : 8; memcpy(head->g1m_mcshead_name, hd.name, len); head->g1m_mcshead_name[len] = 0; /* read specific data */ switch (head->g1m_mcshead_type) { case g1m_mcstype_program:; /* copy password */ head->g1m_mcshead_flags |= g1m_mcsflag_unfinished; end = memchr(hd.aux, 0xFF, 8); len = end ? (size_t)(end - (char*)hd.aux) : 8; memcpy(head->g1m_mcshead_password, hd.aux, len); head->g1m_mcshead_password[len] = 0; log_info("Is a program of %" PRIuFAST32 " bytes", head->g1m_mcshead_size); break; case g1m_mcstype_variable: case g1m_mcstype_matrix: case g1m_mcstype_list: head->g1m_mcshead_height = be16toh(hd.height) & 0xFF; head->g1m_mcshead_width = be16toh(hd.width) & 0xFF; head->g1m_mcshead_count = head->g1m_mcshead_height; if (head->g1m_mcshead_width && head->g1m_mcshead_height) head->g1m_mcshead_flags |= g1m_mcsflag_unfinished; 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, check if is an extended header */ uint8_t buf[39]; READ(buf, 4) uint8_t csum = g1m_checksum8(buf, 4); struct casdyn *dhd = (void*)buf; if (!g1m_maketype_casapp(head, dhd->ext, (char*)dhd->app)) switch (head->g1m_mcshead_info) { case g1m_mcsinfo_cas50: return (decode_cas50(head, buffer, csum)); //case g1m_mcsinfo_cas100: return (decode_cas100(head, buffer)); default: log_error("Platform 0x%04X isn't implemented yet.", head->g1m_mcshead_info); return (g1m_error_op); } /* is a CAS40 head, read it. */ READ(&buf[4], 35) struct cas40 *hd = (void*)buf; csum += g1m_checksum8(&buf[4], 34); log_info("Raw CAS40 (CAS) header:"); logm_info(hd, sizeof(struct cas40)); if (g1m_maketype_cas(head, (char*)hd->data)) return (g1m_error_unrecognized); if (~csum + 1 != hd->checksum) return (g1m_error_checksum); /* fill the handle */ memset(head, 0, sizeof(g1m_mcshead_t)); head->g1m_mcshead_info = g1m_mcsinfo_cas40; memcpy(head->g1m_mcshead_name, hd->filename, 12); head->g1m_mcshead_name[12] = 0; /* type specific things */ if (head->g1m_mcshead_type == g1m_mcstype_program) { struct cas_spe_program *spe = (void*)hd->misc; head->g1m_mcshead_size = be16toh(spe->length); /* TODO: store flags? */ } /* no error! */ return (0); } /* ************************************************************************** */ /* Part decoding functions */ /* ************************************************************************** */ /** * 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->g1m_mcsfile_head.g1m_mcshead_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)); } /* ************************************************************************** */ /* Main file decoding function */ /* ************************************************************************** */ #define READGSIZE() if (grc) { \ uint16_t size; READ(&size, 2) \ log_info("GraphCard next buffer size was: 0x%04X", be16toh(size)); } #define READCOLON() { \ READGSIZE() \ uint8_t colon; GREAD(&colon, 1) \ err = g1m_error_magic; \ if (colon != ':') { \ log_error("Expected ':', got '%c' (0x%02X)", colon, colon); \ goto fail; \ }} /** * decode_cas: * Decode a CAS file. * * 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 h the handle to create. * @arg buffer the buffer to read from. * @arg grc is a graphcard file. * @return the libg1m error. */ static int decode_cas(g1m_handle_t **h, g1m_buffer_t *buffer, int grc) { /* make the handle */ int err = g1m_make_mcs(h, 0); if (err) return (err); g1m_handle_t *handle = *h; /* read each */ for (handle->g1m_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.g1m_mcshead_type == g1m_mcstype_end) break; /* prepare the file */ log_info("Preparing the file."); g1m_mcsfile_t *file; err = g1m_mcs_insert(handle, &file, &head); if (err) goto fail; /* read each part */ #if LOGLEVEL <= ll_info for (int j = 1; file->g1m_mcsfile_head.g1m_mcshead_flags & g1m_mcsflag_unfinished; j++) { #else while (file->g1m_mcsfile_head.g1m_mcshead_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() } /* read last size (check if zero?) */ READGSIZE() /* everything went well :) */ return (0); fail: /* END OF DA WORLD */ g1m_free(handle); return (err); } /* ************************************************************************** */ /* Public decoding functions */ /* ************************************************************************** */ /** * g1m_decode_cas: * Decode a CAS file. * * @arg handle the handle to make. * @arg buffer the buffer to read from. * @return the libg1m error. */ int g1m_decode_cas(g1m_handle_t **handle, g1m_buffer_t *buffer) { return (decode_cas(handle, buffer, 0)); } /** * g1m_decode_grc: * Decode Graph Card file. * * @arg handle the handle to make. * @arg buffer the buffer to read from. * @return the error code (0 if ok). */ int g1m_decode_grc(g1m_handle_t **handle, g1m_buffer_t *buffer) { return (decode_cas(handle, buffer, 1)); }