#include #include #include #include #include #include #include #include #include #include #include #include #include #include #define false 0 #define FALSE 0 #define true 1 #define TRUE 1 // SERIAL char MODEMDEVICE[64]; FILE *tapout_fd = NULL; int is_outfile = 0; int baud_rate = 0; int wait_us = 200; int wait_ms = 800; #define FILENAME_LENGTH 400 char output_dump_file[FILENAME_LENGTH]; char tap_file[FILENAME_LENGTH]; unsigned char in_buff[65538]; unsigned char *p_buff; unsigned int is_continue = 1; unsigned int is_binary = 0; struct pollfd spolfd_serial[1]; // pole descriptoru pro poll static int inp_indx = 0; int serial_fd; size_t out_indx; struct sigaction saterm; /* definition of signal action */ float width; // width of terminal long pos = 0; int scp = 0; int is_scp_read = 0; typedef struct { uint16_t block_len; uint8_t block_xor; uint8_t block_sum; } __attribute__((packed)) fi_sum; typedef struct { uint16_t length; // delka dat ve fileinfo uint8_t h_xor; // xor bajtu fileinfa od fi_numblocks uint8_t h_sum; // sum bajtu fileinfa od fi_numblocks uint8_t fi_numblocks; // pocet bloku uint8_t fi_name[64]; // jmeno souboru - prozatim musi byt ve fromatu 8.3 - kvuli esxdosu uint8_t fi_date[8]; // nepouzito uint8_t fi_time[8]; // nepouzito fi_sum fi_blocks[256]; // delky a soucty bloku } __attribute__((packed)) FILEINFO; FILEINFO fileinfo; void usage(void) { printf ("Tap file over serial port uploader/downloader. (c)2012-2015 Pavel Vymetálek \n"); printf ("Based on utility tapheader: http://zeroteam.sk/tapheader.html (c)2012 Michal Jurica \n"); printf ("Usage:\ntaptoser [-v] [-h] [-o filename.tap] [-b|--baud baud_rate] -d /dev/serial tap_file\n"); printf ("\t-v, --version\tShow version info\n"); printf ("\t-h, --help\tShow this text\n"); printf ("\t-d, --device\tCommunication device\n"); printf ("\t-w, --wait\tWaiting in microseconds between transmitted bytes. Default is -w 200us\n"); printf ("\t-b, --baud\tSet the communication speed. 4800Bd, 9600Bd, 19200Bd and 57600Bd (default) are supported\n"); printf ("\t-B, --binary\tBinary output - received tap from ZX Spectrum will save without all tap's specific mishmash\n"); printf ("\t-o, --outfile\tOutput file. Usualy *.tap or *.bin with in --binary mode\n"); printf ("SCP mode -----\n"); printf ("\t-s, --scp\tSCP mode - sent files with fileinfo 1109bytes\n"); printf ("\t-r, --read\tread file\n"); printf ("\t-w, --wait\tWaiting in milliseconds between transmitted blocks. Default is -w 800 milliseconds\n"); } void Set_DTR(unsigned short level) { int status; ioctl(serial_fd, TIOCMGET, &status); if (level) { status |= TIOCM_DTR; } else { status &= ~TIOCM_DTR; } ioctl(serial_fd, TIOCMSET, &status); } void TestCts(void) { int status; do { ioctl(serial_fd, TIOCMGET, &status); if (status & 0x20) break; usleep (100); if (is_continue == 0) return; // printf ("status: %X\n",status); } while (1); } void TestArgs (int argc, char *argv[]) { int c; while (1) { int option_index = 0; static struct option long_options[] = { {"outfile", required_argument, NULL, 'o'}, {"device", required_argument, NULL, 'd'}, {"baud", required_argument, NULL, 'b'}, {"wait", required_argument, NULL, 'w'}, {"scp", no_argument, NULL, 's'}, {"read", no_argument, NULL, 'r'}, {"binary", no_argument, NULL, 'B'}, {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "o:d:b:w:srBvh", long_options, &option_index); if (c == -1) { // konec parametru break; } switch (c) { case 'h': usage(); exit(1); break; case 'b': baud_rate = atoi(optarg); break; case 'w': wait_us = atoi(optarg); wait_ms = atoi(optarg); break; case 's': printf ("Serial CP mode is activated\n"); scp = 1; break; case 'r': printf ("scp read...\n"); is_scp_read = 1; break; case 'd': strncpy(MODEMDEVICE, optarg, strlen(optarg)); // printf ("Serial port: %s\n", MODEMDEVICE); break; case 'o': strncpy (output_dump_file, optarg, strlen(optarg)); is_outfile = 1; break; case 'B': printf ("Running in binary output mode\n"); is_binary = 1; break; case 'v': printf ("Version 0.9.1 2015-07-22\n"); exit(1); break; default: break; } } if (optind < argc) { while (optind < argc){ strncpy (&tap_file[0], argv[optind++], 63); // input tap file name - without option switch } } } // from tapheader utility unsigned int h_len; unsigned int getword(unsigned char* data) { return *data + 256 * (*(data + 1)); } void tooshort(FILE *fd) { printf("Input file too short.\n\n"); fclose(fd); exit(2); } // decode header1 void decode(unsigned char *header) { char name[11]; unsigned int n; printf("\n"); strncpy(name, (char*)(header+2), 10); name[10] = '\0'; printf("Filename: %12s \n", name); printf("Flag: %4u ", (unsigned int)*header); n = getword(header+14); h_len = getword(header+12); switch(*(header+1)) { case '\0': printf("Type: 0 => program\nProgram length: %6u bytes ", h_len); if (n < 32768) printf("Runs from line %5u\t", n); printf("Length without variables: %5u bytes ", getword(header+16)); break; case '\1': printf("Type: 1 => number array\tLength: %6u bytes", h_len); break; case '\2': printf("Type: 2 => character array\tLength: %6u bytes", h_len); break; case '\3': if (n == 16384 && h_len == 6912) printf("Type: 3 => screen image\n"); else printf("Type: 3 => bytes " "Start address: %5u\t" "Length: %6u bytes\t3rd param: %4u", n, h_len, getword(header+16)); break; } printf("\n"); } void signal_handler_sigterm (int status) { // CTRL+C pressed is_continue = 0; // do not continue } int GetTerminalWidth(void) { struct winsize termsize; ioctl (STDOUT_FILENO, TIOCGWINSZ, &termsize); return (termsize.ws_col); } #define PROGRESS_PERCENT 0 #define PROGRESS_COMPLETTE 1 #define PROGRESS_ERROR_CSUM 2 void DoProgress(size_t pos, size_t max, unsigned char csum_ok) { width = GetTerminalWidth(); float percent, p, m; int imax = width - 40; int ipercent; int px; char progress_char; p = pos; m = max; percent = 100 / m * p; ipercent = percent / 100 * imax; if (is_binary) progress_char = '#'; else progress_char = '='; printf ("Proceed bytes: %6d/%6d [", (int) pos, (int)max); for (px = 0; px < imax; px++) { if (px < ipercent) printf ("%c", progress_char); else printf (" "); } if (csum_ok == 0) { printf ("] %3d %%\r", (int)percent); } else if (csum_ok == 1) { printf ("] OK \r"); } else { printf ("] ERR \r"); } fflush (stdout); } #define HEADER_RAW_LEN 21 void RecvTap() { unsigned int err; size_t len; int result; size_t block_pos = 0, last_block_pos = 0; size_t block_len = 10000; size_t tap_pos = 0; unsigned char xor_csum, xor_csum_ok; unsigned char *p_write; unsigned char block_type; // typ bloku 255 data 0 - hlavicka unsigned char recv_status = 0; // 0 - zacatek, 1 - hlavicka nactena, 2 - blok se nacita, 3- header less tapout_fd = fopen (output_dump_file, "a"); if (tapout_fd == NULL) { err = errno; error (1, err, "can't open output file"); } printf("Output file is: %s\n", output_dump_file); p_buff = in_buff; p_write = in_buff; while (is_continue) { result = poll (spolfd_serial, 1, 200); // 200ms timeout if (result == 0) { // nic neprislo continue; } if (spolfd_serial[0].revents & POLLIN) { len = read (serial_fd, p_buff, 256); block_pos += len; p_buff += len; if (is_binary == 0) { // ulozeni dat ze seriaku jako tap file while (len) { fputc(*p_write, tapout_fd); p_write++; len--; } } if (block_pos != last_block_pos && recv_status >= 2) { last_block_pos = block_pos; DoProgress(block_pos, block_len+2, PROGRESS_PERCENT); } if (block_pos >= HEADER_RAW_LEN && recv_status == 0) { recv_status = 1; // hlavicka nactena block_len = getword(in_buff); block_type = *(in_buff + 2); if (block_type == 0) { // Hlavicka komplet decode (in_buff + 2); block_len = 0; block_pos = 0; xor_csum = 0; recv_status = 0; p_buff = in_buff; p_write = in_buff; } else if (block_type == 255) { // data block printf ("Data block len: %6zu\n", block_len-2); recv_status = 3; } continue; } if (block_pos > HEADER_RAW_LEN + 3 && recv_status == 1) { // nactena delka dalsiho bloku recv_status = 2; block_len = getword(in_buff + HEADER_RAW_LEN); block_type = *(in_buff + HEADER_RAW_LEN + 2); printf ("Block len: %zu Block type: %d\n", block_len, block_type); continue; } if (block_len + 2 == block_pos && recv_status > 2) { // Blok komplet nacten cekej novy block_len = getword(in_buff); block_type = *(in_buff + 2); for (tap_pos = 0; tap_pos < block_len -1; tap_pos++) { xor_csum ^= *(in_buff + 2 + tap_pos); } xor_csum_ok = *(in_buff + 2 + block_len - 1); if (xor_csum == xor_csum_ok) { DoProgress(block_pos, block_len+2, PROGRESS_COMPLETTE); // vypis jeste OK if (is_binary == 1) { // binarni vystup do souboru for (tap_pos = 0; tap_pos < block_len - 2; tap_pos++){ fputc(*(in_buff + 3 + tap_pos), tapout_fd); // uloz do souboru } DoProgress(tap_pos, block_len - 2, PROGRESS_COMPLETTE); } // printf ("Checksum OK\n"); } else { DoProgress(block_pos, block_len+2, PROGRESS_ERROR_CSUM); // vypis error CSUM } block_len = 0; block_pos = 0; xor_csum = 0; recv_status = 0; p_buff = in_buff; p_write = in_buff; continue; } } } fclose (tapout_fd); printf ("\n\nulozeno...\n"); } void SendByte(unsigned char *p_dato) { TestCts(); write (serial_fd, p_dato, 1); // tcdrain(serial_fd); // better solution, but usleep at the next line is usleep (wait_us); // faster solution, TODO change sleep time according to serial communication speed 200us is for 57600Bd } void SendTap() { FILE *tap_fd; unsigned int err, no, len; struct stat st; unsigned char header[19]; no = stat(tap_file, &st); if (no != 0) { err = errno; error(1, err, "can't stat input file"); } tap_fd = fopen(tap_file, "r"); if (tap_fd == NULL) { err = errno; error(1, err, "can't open input file"); } while (pos < st.st_size) { if (is_continue == 0) break; pos += no = fread(header, 1, 2, tap_fd); if (no != 2) tooshort(tap_fd); for (out_indx = 0; out_indx < 2; out_indx++) { SendByte(&header[out_indx]); } no = getword(header); if (no == 19) /* HEADER */ { pos += no = fread(header, 1, 19, tap_fd); if (no != 19) tooshort(tap_fd); // printf ("Header len: %d\n", no); decode(header); for (out_indx = 0; out_indx < 19; out_indx++) { SendByte(&header[out_indx]); } } else { if (h_len != no - 2) { /* zobrazuj iba bloky bez hl. */ len = no; printf ("NO datablock without header: %d\n", no); printf("Type: datablock\nLength: %u\n", len - 2); pos += no = fread(header, 1, 1, tap_fd); SendByte(header); DoProgress(1, no, PROGRESS_PERCENT); if (no != 1) tooshort(tap_fd); printf("Flag: %u\n\n", (int)*header); len--; } else { len = no; } pos += len; for (out_indx = 0; out_indx < len; out_indx++) { fread(header, 1, 1, tap_fd); SendByte(header); DoProgress(out_indx+1, len, PROGRESS_PERCENT); if (is_continue == 0) break; } printf ("\n"); usleep (800000); } } fclose(tap_fd); } int CheckFileInfo(FILEINFO* p_fi) { unsigned char *p_fiinfo = (unsigned char*)p_fi; uint16_t fi_len; uint8_t fi_xor = 0; uint8_t fi_sum = 0; fi_len = p_fi->length; p_fiinfo += 4; // suma se bude pocitat od offsetu 4 for (uint16_t indx = 0; indx < (fi_len-4); indx++) { fi_xor ^= *p_fiinfo; fi_sum += *p_fiinfo; p_fiinfo++; } if (fi_xor == p_fi->h_xor && fi_sum == p_fi->h_sum) { return 0; } return -1; } void CountFileInfoChecksum(FILEINFO* p_fi) { unsigned char *p_fiinfo = (unsigned char*)p_fi; uint16_t fi_len; uint8_t fi_xor = 0; uint8_t fi_sum = 0; fi_len = p_fi->length; p_fiinfo += 4; // suma se bude pocitat od offsetu 4 for (uint16_t indx = 0; indx < (fi_len-4); indx++) { fi_xor ^= *p_fiinfo; fi_sum += *p_fiinfo; p_fiinfo++; } p_fi->h_xor = fi_xor; p_fi->h_sum = fi_sum; } int CheckSumBlock(FILEINFO* p_fi, uint8_t block_indx, uint8_t *p_buffer) { uint16_t block_len; uint8_t b_xor = 0; uint8_t b_sum = 0; uint8_t block_xor; uint8_t block_sum; block_len = p_fi->fi_blocks[block_indx].block_len; block_sum = p_fi->fi_blocks[block_indx].block_sum; block_xor = p_fi->fi_blocks[block_indx].block_xor; for (uint16_t indx = 0; indx < block_len; indx++) { b_xor ^= *p_buffer; b_sum += *p_buffer; p_buffer++; } if (b_xor == block_xor && b_sum == block_sum) { return 0; // vrat se, je to v poradku } return -1; // vrat se s chybou bloku } void CountSumBlock(FILEINFO* p_fi, uint8_t block_indx, uint8_t *p_buffer, uint16_t block_len) { uint8_t block_xor = 0; uint8_t block_sum = 0; for (uint16_t indx = 0; indx < block_len; indx++) { block_xor ^= *p_buffer; block_sum += *p_buffer; p_buffer++; } p_fi->fi_blocks[block_indx].block_len = block_len; p_fi->fi_blocks[block_indx].block_sum = block_sum; p_fi->fi_blocks[block_indx].block_xor = block_xor; } unsigned char buff[32768]; uint32_t GetOverallLen(FILEINFO *p_fi) { uint32_t overall_len = 0; uint8_t block_num = p_fi->fi_numblocks; for (uint8_t indx = 0; indx < block_num; indx++) { overall_len += p_fi->fi_blocks[indx].block_len; } return overall_len; } void RecvSCP(void) { int recv_phase = 0; // 0 - fileinfo, 1 - blok 16kiB, 2 - posledni blok uint8_t block_index = 0; int result; uint16_t len; uint16_t length = 0; uint16_t expected_len = 0; // uint8_t expected_xor; // uint8_t expected_sum; uint32_t overall_length = 0; // celkova delka souboru uint32_t recv_length = 0; // zatim prijatych dat ze souboru int err; FILEINFO *p_fileinfo = &fileinfo; unsigned char *p_buff = buff; tapout_fd = NULL; memset (p_fileinfo, 0, sizeof(fileinfo)); while (is_continue) { result = poll (spolfd_serial, 1, 200); // 200ms timeout if (result == 0) { // nic neprislo continue; } switch (recv_phase) { case 0: // prijem fileinfo if (spolfd_serial[0].revents & POLLIN) { len = read (serial_fd, p_buff, sizeof(fileinfo)); p_buff += len; length += len; if (length == sizeof(fileinfo)) { memcpy((unsigned char*) p_fileinfo, buff, sizeof(fileinfo)); if (CheckFileInfo(p_fileinfo) == 0) { printf("Prijato fileinfo\n"); overall_length = GetOverallLen(p_fileinfo); printf("Nazev souboru: \"%s\" pocet bloku:%d, delka souboru: %u\n", p_fileinfo->fi_name, p_fileinfo->fi_numblocks, overall_length); recv_phase++; // priste se uz prijimaji bloky block_index = 0; // zacina se prvnim blokem p_buff = buff; // buffer na zacatek length = 0; expected_len = p_fileinfo->fi_blocks[block_index].block_len; tapout_fd = fopen ((char*)p_fileinfo->fi_name, "w"); if (tapout_fd == NULL) { err = errno; error (1, err, "can't open output file"); } // expected_xor = p_fileinfo->fi_blocks[block_index].block_xor; // expected_sum = p_fileinfo->fi_blocks[block_index].block_sum; } else { printf("Fileinfo neni v cajku. Koncim...\n"); break; } break; } else if (length > sizeof(fileinfo)){ len = read (serial_fd, p_buff, sizeof(fileinfo)); printf("Nejaky bordel. Koncim...\n"); break; } } break; case 1: // prijem datoveho bloku - max. delka 16kiB if (spolfd_serial[0].revents & POLLIN) { len = read (serial_fd, p_buff, expected_len); p_buff += len; expected_len -= len; length += len; recv_length += len; DoProgress(recv_length, overall_length, PROGRESS_PERCENT); if (length == p_fileinfo->fi_blocks[block_index].block_len) { // prijaty prvni block // printf("Prijaty blok c.%d delky: %d\n", block_index, length); if (CheckSumBlock(p_fileinfo, block_index, buff) == 0) { // blok je v cajku - zapsat do souboru if (tapout_fd) fwrite(buff, length, 1, tapout_fd); } length = 0; p_buff = buff; block_index++; expected_len = p_fileinfo->fi_blocks[block_index].block_len; } if (expected_len == 0 || block_index == 255) { printf ("\nKonec prenosu\n"); recv_phase++; is_continue = 0; break; } } else if (length > 16384) { len = read (serial_fd, p_buff, 16384); printf("Nejaky bordel. Koncim...\n"); break; } } } if (tapout_fd) fclose (tapout_fd); printf ("\n\nulozeno...\n"); } void SendSCP(void) { FILE *tap_fd; unsigned int err, no, len; struct stat st; FILEINFO *p_fileinfo = &fileinfo; unsigned char *p_buff = buff; uint32_t file_len; uint8_t num_blocks = 0; ssize_t odeslano; uint32_t len_sent; uint16_t sent_size; uint32_t overall_sent; no = stat(tap_file, &st); if (no != 0) { err = errno; error(1, err, "can't stat input file"); } tap_fd = fopen(tap_file, "r"); if (tap_fd == NULL) { err = errno; error(1, err, "can't open input file"); } printf ("Soubor %s delky: %ld\n", tap_file, st.st_size); memset (p_fileinfo, 0, sizeof(fileinfo)); // smazat fileinfo memcpy(p_fileinfo->fi_name, tap_file, strlen(tap_file)); file_len = (uint32_t) st.st_size; while (file_len) { len = fread(buff, 1, 16384, tap_fd); // precti 16kiB dat CountSumBlock(p_fileinfo, num_blocks, buff, len); // printf ("Blok c. %d, delka: %d\n", num_blocks, len); file_len -= len; num_blocks++; } p_fileinfo->fi_numblocks = num_blocks; p_fileinfo->length = num_blocks*4 + 85; CountFileInfoChecksum(p_fileinfo); odeslano = write (serial_fd, (void*)p_fileinfo, sizeof(fileinfo)); tcdrain(serial_fd); printf("Fileinfo sent...\n"); // TODO Cekat pauzu mezi bloky usleep (wait_ms * 1000); rewind(tap_fd); // prenaseny soubor na zacatek file_len = (uint32_t) st.st_size; overall_sent = 0; while (file_len && is_continue) { len = fread(buff, 1, 16384, tap_fd); // precti 16kiB dat p_buff = buff; sent_size = 256; len_sent = len; while (len_sent && is_continue) { odeslano = write (serial_fd, (void*)p_buff, sent_size); tcdrain(serial_fd); p_buff += odeslano; overall_sent += odeslano; len_sent -= odeslano; if (len_sent < 256) { sent_size = len_sent; } DoProgress(overall_sent, st.st_size, PROGRESS_PERCENT); } usleep (wait_ms * 1000); file_len -= len; } printf("\nFile sent...\n"); fclose(tap_fd); } int main(int argc, char** argv, char** env) { // osetreni breaku ^C saterm.sa_handler = signal_handler_sigterm; saterm.sa_flags = 0; sigaction (SIGINT, &saterm, NULL); width = GetTerminalWidth(); // nastaveni serioveho portu struct termios oldtio, newtio; inp_indx = 0; if (argc < 3) { printf("You must specify the Serial device and file\n"); usage(); exit(1); } TestArgs (argc, argv); /* open the device to be non-blocking (read will return immediatly) */ serial_fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK); if (serial_fd < 0) { perror(MODEMDEVICE); return(-1); } tcgetattr(serial_fd, &oldtio); /* save current port settings */ switch (baud_rate){ default: baud_rate = 57600; // default speed newtio.c_cflag = B57600 | CS8 | CLOCAL | CREAD | CSTOPB;// | CRTSCTS; break; case 115200: newtio.c_cflag = B115200 | CS8 | CLOCAL | CREAD | CSTOPB | CRTSCTS; break; case 57600: newtio.c_cflag = B57600 | CS8 | CLOCAL | CREAD | CSTOPB | CRTSCTS; break; case 38400: newtio.c_cflag = B38400 | CS8 | CLOCAL | CREAD;// | CRTSCTS; // dva stopbity jsou nastaveny vsude break; case 19200: newtio.c_cflag = B19200 | CS8 | CLOCAL | CREAD | CSTOPB | CRTSCTS; break; case 9600: newtio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CSTOPB | CRTSCTS; break; case 4800: newtio.c_cflag = B4800 | CS8 | CLOCAL | CREAD | CSTOPB | CRTSCTS; break; case 2400: newtio.c_cflag = B2400 | CS8 | CLOCAL | CREAD | CSTOPB | CRTSCTS; break; case 1200: newtio.c_cflag = B1200 | CS8 | CLOCAL | CREAD | CSTOPB | CRTSCTS; break; } printf ("Serial device: %s, communication speed is: %d Bd\n", MODEMDEVICE, baud_rate); // newtio.c_iflag &= ~(IXON | IXOFF | IXANY); // vypne XON/XOFF newtio.c_iflag = 0; //IGNPAR | IXOFF; newtio.c_oflag = 0; // newtio.c_oflag &= ~OPOST; newtio.c_lflag = 0; //NOFLSH; newtio.c_cc[VMIN] = 0; newtio.c_cc[VTIME] = 10; tcsetattr(serial_fd, TCSANOW, &newtio); tcflush(serial_fd, TCIOFLUSH); spolfd_serial[0].fd = serial_fd; // nastaveni hlidaneho descriptoru spolfd_serial[0].events = POLLIN; // hlidaji se data na vstupu if (scp) { // serial copy activated if (is_scp_read) { RecvSCP(); } else { SendSCP(); } } else { Set_DTR(true); // PC can read data from speccy everytime if (is_outfile) { RecvTap(); // cteni tap souboru ze seriaku a zapis na disk pocitace } else { SendTap(); // poslat na seriak tap soubor do spectra } } close (serial_fd); return 0; }