/*
* 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;
}