 /*  This file is part of Wabbitsign
 
 
Wabbitsign 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 2 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, write to the Free Software
     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
     Copyright 2006, Spencer Putt, James Montelongo*/


/* wabbit.c -- Wabbitsign, the Free APP signer for TI-83 Plus
    by Spencer Putt and James Montelongo */


#include <stdio.h>
#include <stdlib.h>
#include <openssl/md5.h> //NOTE: We used Openssl's MD5 generator, but any should do.
//#include <io.h>
//#include <fcntl.h>
#include <string.h>

#include "big.h"


#define TRUE 1
#define FALSE 0

typedef int BOOL;

    FILE* infile;
    FILE* outfile;

unsigned char name [16];
unsigned char fullname [128];
#define name    (header8xk + 17)
#define hleng   sizeof(header8xk)
char header8xk[] = {
    '*','*','T','I','F','L','*','*',    /* required identifier */
    1, 1,                               /* version */
    1, 0x88,                            /* unsure, but always set like this */
    0x01, 0x01, 0x19, 0x97,             /* Always sets date to jan. 1, 1997 */
    8,                                  /* Length of name. */
    0,0,0,0,0,0,0,0};                   /* space for the name */


const char nbuf[]= {
    0xAD,0x24,0x31,0xDA,0x22,0x97,0xE4,0x17,
    0x5E,0xAC,0x61,0xA3,0x15,0x4F,0xA3,0xD8,
    0x47,0x11,0x57,0x94,0xDD,0x33,0x0A,0xB7,
    0xFF,0x36,0xBA,0x59,0xFE,0xDA,0x19,0x5F,
    0xEA,0x7C,0x16,0x74,0x3B,0xD7,0xBC,0xED,
    0x8A,0x0D,0xA8,0x85,0xE5,0xE5,0xC3,0x4D,
    0x5B,0xF2,0x0D,0x0A,0xB3,0xEF,0x91,0x81,
    0xED,0x39,0xBA,0x2C,0x4D,0x89,0x8E,0x87};
const char pbuf[]= {
    0x5B,0x2E,0x54,0xE9,0xB5,0xC1,0xFE,0x26,
    0xCE,0x93,0x26,0x14,0x78,0xD3,0x87,0x3F,
    0x3F,0xC4,0x1B,0xFF,0xF1,0xF5,0xF9,0x34,
    0xD7,0xA5,0x79,0x3A,0x43,0xC1,0xC2,0x1C};
const char qbuf[]= {
    0x97,0xF7,0x70,0x7B,0x94,0x07,0x9B,0x73,
    0x85,0x87,0x20,0xBF,0x6D,0x49,0x09,0xAB,
    0x3B,0xED,0xA1,0xBA,0x9B,0x93,0x11,0x2B,
    0x04,0x13,0x40,0xA1,0x6E,0xD5,0x97,0xB6,0x04};

// q ^ (p - 2))
const char qpowpbuf[] = {
    0xA3,0x82,0x96,0xAF,0x3D,0xDD,0x9B,0x94,
    0xAE,0xA0,0x2F,0x2C,0xE3,0x8B,0xCD,0xD9,
    0xC9,0x11,0x75,0x4F,0x00,0xE4,0xDF,0x47,
    0x38,0xCD,0x98,0x16,0x47,0xF5,0x2B,0x0F};
const char p14buf[] = {
    0x97,0x0B,0x55,0x7A,0x6D,0xB0,0xBF,0x89,
    0xF3,0xA4,0x09,0x05,0xDE,0xF4,0xE1,0xCF,
    0x0F,0xF1,0xC6,0x7F,0x7C,0x7D,0x3E,0xCD,
    0x75,0x69,0x9E,0xCE,0x50,0xB0,0x30,0x07};
const char q14buf[] = {
    0xE6,0x3D,0xDC,0x1E,0xE5,0xC1,0xE6,0x5C,
    0xE1,0x21,0xC8,0x6F,0x5B,0x52,0xC2,0xEA,
    0x4E,0x7B,0xA8,0xEE,0xE6,0x64,0xC4,0x0A,
    0xC1,0x04,0x50,0xA8,0x5B,0xF5,0xA5,0x2D,0x01};

const char fileheader[]= {
    '*','*','T','I','*',0x1A,0x0A,0x00};
const char comment[42]= "grtz2lft,parzival,noice,pokeme,#lsc,TI SUX"; // :)   -pcy
// (yeah, this one isn't mapped into memory afaik, and these 42 bytes are
//  *mandated*... sigh   -pcy)

const char typearray[] = {
    '7','3','*',0x0B,
    '8','2','*',0x0B,
    '8','3','*',0x0B,
    '8','3','F',0x0D,
    '8','5','*',0x00,
    '8','6','*',0x0C,
    '8','5','*',0x00,
    '8','6','*',0x0C,
    };

const char extensions[][4] = {
    "73P","82P","83P","8XP","85P","86P","85S","86S","8XK"};


void makeapp(FILE* infile, FILE* outfile,const char* filename );
int findfield( unsigned char byte, const unsigned char* buffer );
int siggen(const unsigned char* hashbuf, unsigned char* sigbuf, int* outf);
void intelhex( FILE * outfile , const unsigned char* buffer, int size);
void error( const char* msg );
void makeprgm(FILE* infile, FILE* outfile, char* namestring, int calc, char* filestring);
void alphanumeric(char* namestring);

int main(int argc, char *argv[]) {

    int i,nameleng;
    char* namepnt;
/*  puts("\
\xda\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\
\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\
\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xbf\n\
\xb3 Wabbitsign - TI-83+ Series Freeware APP Signer \xb3\n\
\xb3   April 2006, Sixth Release   \t\t \xb3\n\
\xb3   Spencer Putt and James Montelongo\t\t \xb3\n\
\xc0\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\
\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\
\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xd9\n"); */
    puts(
"  .--------------------------------------------------.\n"
"  |  Wabbitsign - TI-83+ Series Freeware APP Signer  |\n"
"  |    April 2006, Sixth Release                     |\n"
"  |    Spencer Putt and James Montelongo             |\n"
"  |      Contains minor tweaks by PoroCYon/K2^TiTAN  |\n"
"  |      (July 2019)                                 |\n"
"  `--------------------------------------------------'\n");

/* Get binary in file */
    if (argc < 3){
        printf("\
Usage:\n %s <input file.bin> <output file.8XK|8XP|86S|86P|85S|85P|83P|82P|73P>\n\
 <input file>  must be binary or IntelHex.\n\
 <output file> extension determines output format.\n\
   e.g. \"%s test.bin test.8xp\" generates a TI-83+ program file.\n",argv[0],argv[0]);
        return 1;
    }
    if (!(infile = fopen(argv[1],"rb"))) error("Could not open in file.");
    if (!(outfile = fopen(argv[2],"wb"))) error("Could not open out file.");
/* Handle Directory output */
    for (i = strlen(argv[2])-1; i && argv[2][i]!='\\'; i--);
    if (i) strcpy(argv[2],argv[2]+i+1);
    strcpy(fullname,argv[2]);   // save name for echo output
    for(i = 0; argv[2][i]!='.' && argv[2][i]; i++); // find extension
    if (!argv[2][i]) error("Output file name does not have an extension");
    nameleng = i;
    argv[2][i] = 0;
    namepnt = argv[2]+i+1;
    alphanumeric(namepnt);//upper case it
/* compare to predefined extensions */
    for(i = 0; i < sizeof(extensions)/4 && strcmp(namepnt,extensions[i]); i++);
//    printf("%d\n",i);
    if (i < 8) {
        if (nameleng > 8) error("Output file name is too long.\n It must only be 8 characters w/o extension");
        makeprgm(infile,outfile,argv[2],i,fullname);
    } else if (i == 8) {
        makeapp(infile,outfile,fullname);
    } else error("Output Extension invalid!");
    fclose(infile);
    fclose(outfile);
    return 0;
}


/* Handles errors */
void error( const char* msg ) {
    printf("Error: %s\n",msg);
    if (outfile) {
        fclose(outfile);
        remove(fullname);
    }    
    exit(1);
}


void makeapp(FILE* infile, FILE* outfile,const char* filename ) {
    unsigned char * buffer;
    unsigned char hashbuf[16];
    unsigned char linebuf[550];
    int i,pnt,siglength,tempnum,f,pages,highest,temp;
    unsigned int size, total_size,numbytes,bufptr,rectype;
    
    tempnum = fgetc(infile);
    rewind(infile);
    if (tempnum!=0x80) {
        pages = 0;
        rectype = 0;
        highest = 0;
        while (rectype!=1 && fgets(linebuf,548,infile)) {
            if (strlen(linebuf) < 9) continue;
            sscanf(linebuf,":%*02X%*04X%02X%*02X%02X%*s",&rectype,&temp);
            if (rectype == 2 && temp >= pages) {
                pages = temp;
                highest = (unsigned int) ftell(infile);
            }
        }
 
         /* Find the highest page record again (if pages != -1, then there was a page record */
        fseek(infile,(long) highest,SEEK_SET);

        /* Run through the string of data records */
        highest = 0x4000;
        rectype = 0;
        while (!rectype && fgets(linebuf,548,infile)) {
            if (strlen(linebuf) < 9) continue;
            sscanf(linebuf,":%02X%04X%02X%*s",&numbytes,&bufptr,&rectype);
            if (!rectype && highest < bufptr + numbytes) highest = bufptr + numbytes;
        }
        /* allocated just enough space for the binary */
        size = pages*16384 + (highest - 0x4000);
        buffer = (unsigned char*) malloc(size + 256);
        if (!buffer) error("Insignificant memory.");
        
        rewind(infile);
        
        /* Write data records to the appropriate part of the buffer */
        rectype = 0;
        pages = 0;
        while (rectype!=1 && fgets(linebuf,548,infile)) {
            if (strlen(linebuf) < 9) continue;
            
            sscanf(linebuf,":%02X%04X%02X%*s",&numbytes,&bufptr,&rectype);
            if (rectype==2) sscanf(linebuf+11,"%02X",&pages);
            else if (!rectype) {
                if ((bufptr + numbytes)>0x8000 ||
                        bufptr<0x4000) error("An address is out of bounds.");
                bufptr += -0x4000 + (pages*16384);
                for(i = 0; i < 2*numbytes; i+=2) {
                    sscanf(linebuf+9+i,"%02X",&temp);
                    buffer[bufptr++] = temp;
                }
            }
        }
    } else {
        /* Calculate the size */
        fseek(infile,0,SEEK_END);
        size = ftell(infile);
        rewind(infile);
        /* Copy file to memory */
        if (!(buffer = (unsigned char*) malloc (size+256))) error ("Insufficient memory.");
        for(i = 0; i < size; i++) buffer[i] = fgetc(infile);
    }


/* Check if size will fit in mem with signature */
    if (tempnum = ((size+96)%16384)) {
        if (tempnum < 97) error("The last page must have room for the signature!\n Roughly 96 bytes.");
        if (tempnum<1024 && (size+96)>>14) printf("Warning: There are only %d bytes on the last page.\n", tempnum);
    }

/* Fix app header fields */
/* Length Field: set to size of app - 6 */
    if (((unsigned short*)buffer)[0] != 0x0F80)
        error("Length field not present.");
    size -= 6;
    buffer[2] = size >> 24;         //Stored in Big Endian
    buffer[3] = (size>>16) & 0xFF;
    buffer[4] = (size>> 8) & 0xFF;
    buffer[5] = size & 0xFF;
    size += 6;
/* Program Type Field: Must be present and shareware (0104) */
    pnt = findfield(0x12, buffer);
    if (!pnt || ( buffer[pnt++]!=1) || (buffer[pnt]!=4) ) 
        error("Program type field missing or incorrect.");
/* Pages Field: Corrects page num*/
    pnt = findfield(0x81, buffer);
    if (!pnt) 
        error("Pages field missing.");
    
    pages = size>>14; /* this is safe because we know there's enough room for the sig */
    if (size & 0x3FFF) pages++;
    buffer[pnt] = pages;
/* Name Field: MUST BE 8 CHARACTERS, no checking if valid */
    pnt = findfield(0x48, buffer);
    if (!pnt)
        error("Name field missing.");
    for (i=0; i < 8 ;i++) name[i]=buffer[i+pnt];

/* Calculate MD5 */
    MD5(buffer, size, hashbuf);  //This uses ssl but any good md5 should work fine.

/* Generate the signature to the buffer */
    siglength = siggen(hashbuf, buffer+size+3, &f );

/* append sig */
    buffer[size + 0] = 0x02;
    buffer[size + 1] = 0x2d;
    buffer[size + 2] = (unsigned char) siglength;
    total_size = size + siglength + 3;
    if (f) {
        buffer[total_size++] = 1;
        buffer[total_size++] = f;
    } else buffer[total_size++] = 0;
/* sig must be 96 bytes ( don't ask me why) */
    tempnum = 96 - (total_size - size);
    while (tempnum--) buffer[total_size++] = 0xFF;

/* Do 8xk header */
    for (i = 0; i < hleng; i++) fputc(header8xk[i], outfile);
    for (i = 0; i < 23; i++)    fputc(0, outfile);
    fputc(0x73, outfile);
    fputc(0x24, outfile);
    for (i = 0; i < 24; i++)    fputc(0, outfile);
    tempnum =  77 * (total_size>>5) + pages * 17 + 11;
    size = total_size & 0x1F;
    if (size) tempnum += size<<1 + 13;
    fputc( tempnum & 0xFF, outfile); //little endian
    fputc((tempnum >> 8) & 0xFF, outfile);
    fputc((tempnum >> 16)& 0xFF, outfile);
    fputc( tempnum >> 24, outfile);
    
/* Convert to 8xk */
    intelhex(outfile, buffer, total_size);

    if (pages==1) printf("%s (%d page",filename,pages);
    else printf("%s (%d pages",filename,pages);
	puts(") was successfully generated!");
}




/* Starting from 0006 searches for a field
 * in the in file buffer. */
int findfield( unsigned char byte, const unsigned char* buffer ) {
    int pnt=6;
    while (buffer[pnt++] == 0x80) {
        if (buffer[pnt] == byte) {
            pnt++;
            return pnt;
        } else
            pnt += (buffer[pnt]&0x0F);
        pnt++;
    }
    return 0;
}

/* Gmp was originally used by Ben Moody, but due to it's massive size
 * Spencer wrote his own big num routines,  cutting the size to a tenth 
 * of what it was. */
int siggen(const unsigned char* hashbuf, unsigned char* sigbuf, int* outf) {
    big mhash, n, p, q, r, s, temp, result;
    unsigned int lp,lq;
    int i,siglength;
    
/* Intiate vars */
    mhash = big_create();
    p  = big_create();
    q = big_create();
    r = big_create();
    s = big_create();
    temp = big_create();
    result = big_create();

/* Import vars */
    big_read(mhash,hashbuf,16);
    big_read(p,pbuf,sizeof(pbuf));
    big_read(q,qbuf,sizeof(qbuf));

/*---------Find F----------*/
/*      M' = m*256+1      */
    for (i = 0; i < 8; i++)
    big_sll(mhash);
    big_add_ui(mhash,mhash,1);

/* calc f {2, 3,  0, 1 }  */
    lp = ((big_legendre(mhash,p)==1)?0:1);
    lq = ((big_legendre(mhash,q)==1)?1:0);
    *outf = lp+lq+lq;

/*apply f */
    if (lp==lq) big_sll(mhash);
    if (lq==0) {
        big_read(temp,nbuf,sizeof(nbuf));
        big_sub(mhash,temp,mhash);
    }

/* r = ( M' ^ ( ( p + 1) / 4 ) ) mod p */
    big_read(result,p14buf,sizeof(p14buf)); //Several numbers are precalculated to save time
    big_powm(r,mhash,result,p);
    
/* s = ( M' ^ ( ( q + 1) / 4 ) ) mod q */
    big_read(result,q14buf,sizeof(q14buf));
    big_powm(s,mhash,result,q);
    
/* r-s */
    big_set_ui(temp,0);
    big_sub(temp, r, s);
    
/* q ^ (p - 2)) */
    big_read(result,qpowpbuf,sizeof(qpowpbuf));

/* (r-s) * q^(p-2) mod p */
    big_mul(temp, temp, result);
    big_mod(temp, temp, p);

/* ((r-s) * q^(p-2) mod p) * q + s */
    big_mul(result, temp, q);
    big_add(result, result, s);

/* export sig */
    //mpz_export(sigbuf, siglength, -1, 1, 0, 0, result);
    for (i = 0; i < kMaxLength; i++) {
        if (result->number[i]) siglength = i;
    }
    siglength++;
//    for (siglength = 0; ( (siglength < kMaxLength) && (result->number[i]) ); siglength++); 
    for (i = 0; i < siglength; i++) sigbuf[i] = result->number[i];
/* Clean Up */
    big_clear(p);
    big_clear(q);
    big_clear(r);
    big_clear(s);
    big_clear(temp);
    big_clear(result);
    return siglength;
}


void makeprgm(FILE* infile, FILE* outfile, char* namestring, int calc, char* filestring) {
    int i,size,temp;
    unsigned char* pnt;
    int chksum;
    /* The name must be Capital lettes and numbers */
    if (calc<4) {
        alphanumeric(namestring);
    }
    /* get size */
    fseek(infile,0,SEEK_END);
    size = ftell(infile)+2;
    rewind(infile);
    /* 86ers don't need to put the asm token*/
    if (calc==5) {
	size+=2;
    }
    /* size must be smaller than z80 mem */
    if (size > 24000) {
        if (size > 65000) error("File size is greater than 64k");
        puts("Warning: Files size greater than 24k");
    }
    
    /* Lots of pointless header crap */
    for(i = 0; i < 4; i++) fputc(fileheader[i],outfile);
    pnt = ((unsigned char*)typearray+(calc<<2));
    fputc(pnt[0],outfile);
    fputc(pnt[1],outfile);
    fputc(pnt[2],outfile);
    fputc(fileheader[i++],outfile);
    fputc(fileheader[i++],outfile);
    if (calc==4) {
        fputc(0x0C,outfile);
    }else {
        fputc(fileheader[i++],outfile);
    }    
    fputc(fileheader[i++],outfile);
    
    
    for (i = 0; i < 42; i++) fputc(comment[i],outfile);

    /* For some reason TI thinks it's important to put the file size */
    /* dozens of times, I suppose duplicates add secrutiy...*/
    /* yeah right. */
    if (calc==4) {
        temp=size+8+strlen(namestring);
    } else {
        temp=size+15+((calc==3)?2:0)+((calc==5)?1:0);
    }
    fputc(temp & 0xFF,outfile);
    fputc(temp >> 8,outfile);
    if (calc==4) {
        chksum = fputc((6+strlen(namestring)),outfile);
    } else {
        chksum = fputc(pnt[3],outfile);
    }
    fputc(0,outfile);
    /* OMG the Size again! */
    chksum += fputc(size & 0xFF,outfile);
    chksum += fputc(size>>8,outfile);
    if ( calc>=4) {
        if (calc>=6) {
            chksum += fputc(0x0C,outfile);
        } else {
            chksum += fputc(0x12,outfile);
        }
        chksum += fputc(strlen(namestring),outfile);
    } else {
        chksum += fputc(6,outfile);
    }
    
    /* The actual name is placed with padded with zeros */
    if (calc<4) { //i know...just leave it for now.
        if (!((temp=namestring[0])>='A' && temp<='Z')) error("First character in name must be a letter.");
    }
    for(i = 0; i < 8 && namestring[i]; i++) chksum += fputc(namestring[i], outfile);
    if (calc != 4 && calc!=6) {
        for(;i < 8; i++) fputc(0,outfile);
    }
    /* 83+ requires 2 extra bytes */
    if (calc==3) {
        fputc(0,outfile);
        fputc(0,outfile);
    }
    /*Yeah, lets put the size twice in a row  X( */
    chksum += fputc(size & 0xFF,outfile);
    chksum += fputc(size>>8,outfile);
    size-=2;
    chksum += fputc(size & 0xFF,outfile);
    chksum += fputc(size>>8,outfile);
    
    /* check for BB 6D on 83+ */
    if (calc == 3) {
        temp = fgetc(infile);
        if (!(temp == 0xBB && ( fgetc(infile) == 0x6D ))) puts("Warning: 83+ program does not begin with bytes BB 6D.");
        fseek(infile,0,SEEK_SET);
    }
    if (calc == 5) {
	   chksum += fputc(0x8E,outfile);
	   chksum += fputc(0x28,outfile);
	   size -= 2;
    }
    /* Actual program data! */
    for(i = 0; i < size; i++) {
        chksum += fputc(fgetc(infile),outfile);
    }
    /* short little endian Checksum */
    fputc(chksum & 0xFF,outfile);
    fputc((chksum >> 8) & 0xFF,outfile);
    printf("%s (%d bytes) was successfully generated!\n",filestring,size);
}


void alphanumeric(char* namestring) {
    int i;
    char temp;

    while (temp = *namestring) {
        if (temp>='a' && temp<='z') *namestring = temp =(temp-('a'-'A'));
        if (!( ((temp>='A') && (temp<='Z')) || (temp>='0' && temp<='9') || (temp==0) ) ) {
            error("Invalid characters in name. Alphanumeric Only.");
        }
        namestring++;
    }
}


/* Convert binary buffer to intel hex in ti format
 * All pages addressed to $4000 and are only $4000
 * bytes long. */
void intelhex( FILE * outfile , const unsigned char* buffer, int size) {
    const char hexstr[16] = "0123456789ABCDEF";
    int page = 0;
    int bpnt = 0;
    unsigned int address,ci,temp,i;
    unsigned char chksum;
    unsigned char outbuf[128];
    
    //We are in binary mode, we must handle carridge return ourselves.
   
    while(bpnt < size){
        fprintf(outfile,":02000002%04X%02X\r\n",page,(unsigned char) ( (~(0x04 + page)) +1));
        page++;
        address = 0x4000;   
        for (i = 0; bpnt < size && i < 512; i++) {
             chksum = (address>>8) + (address & 0xFF);
             for(ci = 0; ((ci < 64) && (bpnt < size)); ci++) {
                temp = buffer[bpnt++];
                outbuf[ci++] = hexstr[temp>>4];
                outbuf[ci] = hexstr[temp&0x0F];
                chksum += temp;
            }
            outbuf[ci] = 0;
            ci>>=1;
            fprintf(outfile,":%02X%04X00%s%02X\r\n",ci,address,outbuf,(unsigned char)( ~(chksum + ci)+1));
            address +=0x20;
        }         
    }
    fprintf(outfile,":00000001FF");
}
