521 lines
12 KiB
C
521 lines
12 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#else
|
|
# ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
# 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;
|
|
}
|
|
|