/* * RabbitSign - Tools for signing TI graphing calculator software * Copyright (C) 2009 Benjamin Moody * * 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 3 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include #ifdef HAVE_STRING_H # include #else # ifdef HAVE_STRINGS_H # include # endif #endif #include "rabbitsign.h" #include "internal.h" /* * Determine the type of an unknown program, if possible. */ static void guess_type(RSProgram* prgm, int is_hex) { const unsigned char* hdr; unsigned long hdrstart, hdrsize, keyid, fieldstart, fieldsize; /* Z80 OSes have a detached program header */ if (prgm->header_length > 2 && prgm->header[0] == 0x80) { rs_get_field_size(prgm->header, &hdrstart, NULL); hdr = prgm->header + hdrstart; hdrsize = prgm->header_length - hdrstart; keyid = rs_get_numeric_field(0x8010, hdr, hdrsize); prgm->datatype = RS_DATA_OS; if ((keyid & 0xff) == 0x02) { prgm->calctype = RS_CALC_TI73; } else { prgm->calctype = RS_CALC_TI83P; } } else if (prgm->length > 2) { rs_get_field_size(prgm->data, &hdrstart, NULL); hdr = prgm->data + hdrstart; hdrsize = prgm->length - hdrstart; if (hdrsize > 128) hdrsize = 128; /* Z80 apps and 68k OSes have field type 0x8000 */ if (prgm->data[0] == 0x80 && (prgm->data[1] & 0xf0) == 0x00) { keyid = rs_get_numeric_field(0x8010, hdr, hdrsize); switch (keyid & 0xff) { case 0x02: prgm->calctype = RS_CALC_TI73; prgm->datatype = RS_DATA_APP; break; case 0x04: case 0x0A: prgm->calctype = RS_CALC_TI83P; prgm->datatype = RS_DATA_APP; break; case 0x03: case 0x09: prgm->calctype = RS_CALC_TI89; prgm->datatype = RS_DATA_OS; break; case 0x01: case 0x08: prgm->calctype = RS_CALC_TI92P; prgm->datatype = RS_DATA_OS; break; default: if (is_hex) { prgm->calctype = RS_CALC_TI83P; prgm->datatype = RS_DATA_APP; } break; } } /* 68k apps have field type 0x8100 */ else if (prgm->data[0] == 0x81 && (prgm->data[1] & 0xf0) == 0x00) { keyid = rs_get_numeric_field(0x8110, hdr, hdrsize); prgm->datatype = RS_DATA_APP; switch (keyid & 0xff) { case 0x03: case 0x09: prgm->calctype = RS_CALC_TI89; break; case 0x01: case 0x08: prgm->calctype = RS_CALC_TI92P; break; } } /* Certificates have field type 0x0300 */ else if (prgm->data[0] == 0x03 && (prgm->data[1] & 0xf0) == 0x00) { prgm->datatype = RS_DATA_CERT; if (!rs_find_app_field(0x0400, hdr, hdrsize, NULL, &fieldstart, &fieldsize) && fieldsize >= 1) { switch (hdr[fieldstart]) { case 0x02: prgm->calctype = RS_CALC_TI73; break; case 0x04: case 0x0A: prgm->calctype = RS_CALC_TI83P; break; case 0x03: case 0x09: prgm->calctype = RS_CALC_TI89; break; case 0x01: case 0x08: prgm->calctype = RS_CALC_TI92P; break; } } } } } /* * Read the contents of a binary file into an RSProgram. */ static int read_file_binary(RSProgram* prgm, FILE* f, unsigned long filesize) { unsigned char buf[1024]; size_t count; if (filesize) { while (filesize > 0) { if (filesize > 1024) count = fread(buf, 1, 1024, f); else count = fread(buf, 1, filesize, f); if (count > 0) rs_program_append_data(prgm, buf, count); else break; filesize -= count; } } else { do { count = fread(buf, 1, 1024, f); if (count > 0) { rs_program_append_data(prgm, buf, count); } } while (count > 0); } if (!prgm->calctype || !prgm->datatype) guess_type(prgm, 0); return RS_SUCCESS; } /* * Find a given page in the list of page numbers (or add it to the * end.) */ static int getpageidx(RSProgram* prgm, /* program */ unsigned int pagenum) /* page number */ { int i; unsigned int* array; for (i = 0; i < prgm->npagenums; i++) if (prgm->pagenums[i] == pagenum) return i; if (!(array = rs_realloc(prgm->pagenums, (i + 1) * sizeof(unsigned int)))) return 0; prgm->pagenums = array; prgm->npagenums = i + 1; prgm->pagenums[i] = pagenum; return i; } /* * Read an Intel/TI hex file into an RSProgram. * * Note that the first ':' is assumed to have been read already. */ static int read_file_hex(RSProgram* prgm, FILE* f, unsigned int flags) { int c; unsigned int nbytes, addr, rectype, sum, i, b, value; unsigned int pagenum = 0, pageidx = 0, lastaddr = 0; unsigned long offset; unsigned char data[256]; unsigned char* sigp; int nparts = 0; int possibly_os_header = 1; rs_free(prgm->pagenums); if (!(prgm->pagenums = rs_malloc(sizeof(unsigned int)))) return RS_ERR_OUT_OF_MEMORY; prgm->pagenums[0] = 0; prgm->npagenums = 1; while (!feof(f) && !ferror(f)) { if (3 > fscanf(f, "%2X%4X%2X", &nbytes, &addr, &rectype)) { rs_error(NULL, prgm, "invalid hex data (following %X:%X)", pagenum, lastaddr); return RS_ERR_HEX_SYNTAX; } /* Read data bytes */ sum = nbytes + addr + (addr >> 8) + rectype; value = 0; for (i = 0; i < nbytes; i++) { if (1 > fscanf(f, "%2X", &b)) { rs_error(NULL, prgm, "invalid hex data (at %X:%X)", pagenum, addr); return RS_ERR_HEX_SYNTAX; } data[i] = b; sum += b; value = (value << 8) + b; } /* Read checksum */ c = fgetc(f); if (c == 'X') { c = fgetc(f); if (c != 'X') { rs_error(NULL, prgm, "invalid hex data (at %X:%X)", pagenum, addr); return RS_ERR_HEX_SYNTAX; } } else { ungetc(c, f); if (1 > fscanf(f, "%2X", &b)) { rs_error(NULL, prgm, "invalid hex data (at %X:%X)", pagenum, addr); return RS_ERR_HEX_SYNTAX; } sum += b; if (sum & 0xff) rs_warning(NULL, prgm, "incorrect checksum (at %X:%X)", pagenum, addr); } if (rectype == 0 && nbytes > 0) { /* Record type 0: program data */ if (addr & 0xff00) possibly_os_header = 0; addr &= 0x3fff; /* if program does not start at addr 0000 (or 4000), assume unsorted */ if (addr && prgm->length == 0) flags &= ~RS_INPUT_SORTED; if ((flags & RS_INPUT_SORTED) && !addr && lastaddr) { /* automatically switch to next page */ pagenum++; pageidx = getpageidx(prgm, pagenum); if (!pageidx) return RS_ERR_OUT_OF_MEMORY; } else if (addr < lastaddr) flags &= ~RS_INPUT_SORTED; if (nparts == 2 && prgm->header_length) { /* Reading an OS signature */ if (addr + nbytes > prgm->signature_length) { if (!(sigp = rs_realloc(prgm->signature, addr + nbytes))) return RS_ERR_OUT_OF_MEMORY; prgm->signature = sigp; if (addr > prgm->signature_length) { memset(prgm->signature + prgm->signature_length, 0xff, addr - prgm->signature_length); } prgm->signature_length = addr + nbytes; } memcpy(prgm->signature + addr, data, nbytes); } else { /* Reading normal program data */ offset = ((unsigned long) pageidx << 14) | addr; if (offset + nbytes <= prgm->length) { memcpy(prgm->data + offset, data, nbytes); } else { rs_program_set_length(prgm, offset); rs_program_append_data(prgm, data, nbytes); } } lastaddr = addr; } else if (rectype == 1) { /* Record type 1: "end of file" */ nparts++; if (nparts == 3 && prgm->header_length) break; } else if (rectype == 2 || rectype == 4) { /* Record type 2 or 4: extended address */ possibly_os_header = 0; flags &= ~RS_INPUT_SORTED; if (nparts < 2) { pagenum = value; pageidx = getpageidx(prgm, pagenum); if (pagenum && !pageidx) return RS_ERR_OUT_OF_MEMORY; } } do { c = fgetc(f); } while (c == '\n' || c == '\r' || c == ' '); if (c == EOF) break; else if (c != ':') { if (rectype == 1) break; else { rs_error(NULL, prgm, "invalid hex data (following %X:%X)", pagenum, lastaddr); return RS_ERR_HEX_SYNTAX; } } if (rectype == 1 && nparts == 1 && prgm->length > 0 && possibly_os_header) { /* Just finished reading OS header */ flags &= ~RS_INPUT_SORTED; pagenum = pageidx = 0; rs_free(prgm->header); if (!(prgm->header = rs_malloc(prgm->length))) return RS_ERR_OUT_OF_MEMORY; memcpy(prgm->header, prgm->data, prgm->length); prgm->header_length = prgm->length; prgm->length = 0; possibly_os_header = 0; } } if (!prgm->calctype || !prgm->datatype) guess_type(prgm, 1); return RS_SUCCESS; } /* * Check if calc/data type matches expected type (or any recognized * type, if none was specified.) */ static int check_tifl_type(int calctype, int datatype, int calctype_expected, int datatype_expected) { if (calctype_expected) { if (calctype_expected != calctype) return 0; } else { if (calctype != RS_CALC_TI73 && calctype != RS_CALC_TI83P && calctype != RS_CALC_TI89 && calctype != RS_CALC_TI92P) return 0; } if (datatype_expected) { if (datatype_expected != datatype) return 0; } else { if (datatype != RS_DATA_APP && datatype != RS_DATA_OS) return 0; } return 1; } /* * Read program contents from a file. * * Various file formats are supported: * * - Raw binary (must begin with the value 0x80 or 0x81) * - Plain Intel/TI hex * - Binary TIFL (89k, 89u, ...) * - Hex TIFL (8xk, 8xu, ...) * * Note: on platforms where it matters, all input files must be opened * in "binary" mode. */ int rs_read_program_file(RSProgram* prgm, /* program */ FILE* f, /* file */ const char* fname, /* file name */ unsigned int flags) /* option flags */ { int c; unsigned char tiflbuf[78]; unsigned long tiflsize, i; int e; rs_program_set_length(prgm, 0); prgm->header_length = 0; prgm->signature_length = 0; prgm->npagenums = 0; rs_free(prgm->filename); prgm->filename = rs_strdup(fname); if (fname && !prgm->filename) return RS_ERR_OUT_OF_MEMORY; if (flags & RS_INPUT_BINARY) return read_file_binary(prgm, f, 0); c = fgetc(f); if (c == 0x80 || c == 0x81) { tiflbuf[0] = c; if ((e = rs_program_append_data(prgm, tiflbuf, 1))) return e; return read_file_binary(prgm, f, 0); } while (!feof(f) && !ferror(f)) { if (c == ':') { return read_file_hex(prgm, f, flags); } else if (c == '*') { if (fread(tiflbuf, 1, 78, f) < 78 || strncmp((char*) tiflbuf, "*TIFL**", 7)) { rs_error(NULL, prgm, "unknown input file format"); return RS_ERR_UNKNOWN_FILE_FORMAT; } tiflsize = ((unsigned long) tiflbuf[73] | ((unsigned long) tiflbuf[74] << 8) | ((unsigned long) tiflbuf[75] << 16) | ((unsigned long) tiflbuf[76] << 24)); if (check_tifl_type(tiflbuf[47], tiflbuf[48], prgm->calctype, prgm->datatype)) { prgm->calctype = tiflbuf[47]; prgm->datatype = tiflbuf[48]; if (tiflbuf[77] == ':') return read_file_hex(prgm, f, 0); else { if ((e = rs_program_append_data(prgm, tiflbuf + 77, 1))) return e; return read_file_binary(prgm, f, tiflsize ? tiflsize - 1 : 0); } } else { /* extra data (license, certificate, etc.) -- ignore */ if (fseek(f, tiflsize - 1, SEEK_CUR)) { for (i = 0; i < tiflsize - 1; i++) { if (fgetc(f) == EOF) { rs_error(NULL, prgm, "unexpected EOF"); return RS_ERR_UNKNOWN_FILE_FORMAT; } } } } } c = fgetc(f); } rs_error(NULL, prgm, "unknown input file format"); return RS_ERR_UNKNOWN_FILE_FORMAT; }