Minor edits made while composing the previous commit that added checksums.
[pdp11-bin2load] / bin2load.c
index 5764c9f..9786ecd 100644 (file)
 #include <errno.h>
 #include <getopt.h>
 
 #include <errno.h>
 #include <getopt.h>
 
-#define VERSION 1
+#define VERSION 
 
 void
 
 void
-print_usage( char ** argv )
+print_usage(char ** argv)
 {
     printf( "bin2load v%d (www.subgeniuskitty.com)\n"
 {
     printf( "bin2load v%d (www.subgeniuskitty.com)\n"
-            "Usage: %s i <file> -o <file> [-a <address>]\n"
-            "  -i <file>    Binary file to be written to tape.\n"
-            "               For example, binary executable from 'pdp11-aout-objdump'.\n"
-            "  -o <file>    New paper tape image for use with SIMH.\n"
-            "  -a <address> Address on PDP-11 at which to load paper tape contents.\n"
+            "Usage: %s -i <file> -o <file> [-a <address>]\n"
+            "  -i <file>    Raw binary file to be written to tape.\n"
+            "               For example, output from 'pdp11-aout-objdump' (see README.md).\n"
+            "  -o <file>    Output file created by bin2load containing tape image for use with SIMH.\n"
+            "  -a <address> (optional) Address on PDP-11 at which to load tape contents.\n"
             , VERSION, argv[0]
     );
 }
 
             , VERSION, argv[0]
     );
 }
 
-
 int
 int
-main( int argc, char ** argv)
+main(int argc, char ** argv)
 {
     int c;
     FILE * src = NULL;
     FILE * dst = NULL;
 {
     int c;
     FILE * src = NULL;
     FILE * dst = NULL;
-    uint16_t address = 01000; /* Default address to load tape contents. */
+    uint16_t address = 01000; /* Default address to load tape contents in RAM. */
 
     while ((c = getopt(argc, argv, "i:o:a:h")) != -1) {
         switch (c) {
 
     while ((c = getopt(argc, argv, "i:o:a:h")) != -1) {
         switch (c) {
@@ -62,45 +61,117 @@ main( int argc, char ** argv)
         print_usage(argv);
         exit(EXIT_FAILURE);
     }
         print_usage(argv);
         exit(EXIT_FAILURE);
     }
+
     printf("Paper tape will load at address 0%o.\n", address);
 
     /*
     printf("Paper tape will load at address 0%o.\n", address);
 
     /*
-     * Paper tape format(bytes, not words):
-     *   01
-     *   00
-     *   Low byte of packet length (binary length + 6 for header)
-     *   High byte of packet length
-     *   Low byte of address to load binary data at
-     *   High byte of address
-     *   DataBegin
-     *    |
-     *    |
-     *   DataEnd
-     *   Checksum
+     * SIMH Binary Loader Format
+     *
+     * Loader format consists of blocks, optionally preceded, separated, and
+     * followed by zeroes. Each block consists of the following entries. Note
+     * that all entries are one byte.
+     *
+     *     0001
+     *     0000
+     *     Low byte of block length (data byte count + 6 for header, excludes checksum)
+     *     High byte of block length
+     *     Low byte of load address
+     *     High byte of load address
+     *     Data byte 0
+     *       ...
+     *     Data byte N
+     *     Checksum
      *
      *
-     * I am unsure of the checksum format and need to check the SIMH source.
-     * In the meantime a zero checksum still functions, albeit with a warning.
+     * The 8-bit checksum for a block is the twos-complement of the lower eight
+     * sum bits for all six header bytes and all data bytes.
+     *
+     * If the block length is exactly six bytes (i.e. only header, no data),
+     * then the block marks the end-of-tape. The checksum should be zero.  If
+     * the load address of this final block is not 000001, then it is used as
+     * the starting PC.
      */
 
      */
 
-    uint16_t size = 6;
+    uint32_t checksum = 0;
+    uint32_t size = 6;
+    uint8_t data;
 
 
-    uint16_t header[] = {1,0,01000};
-    fwrite(header,6,1,dst);
+    /* Write header for data block. */
+    for (int i = 0; i < size; i++) {
+        switch (i) {
+            case 0: data = 0001;                  break;
+            case 1: data = 0000;                  break;
+            case 2: data = 0000;                  break; /* Size will be populated later */
+            case 3: data = 0000;                  break; /* Size will be populated later */
+            case 4: data = address & 0xff;        break;
+            case 5: data = (address >> 8) & 0xff; break;
+        }
+        if (!fwrite(&data, 1, 1, dst)) {
+            fprintf(stderr, "ERROR: Failed to write block header.\n");
+            exit(EXIT_FAILURE);
+        }
+        checksum += data;
+    }
 
 
-    uint8_t byte;
-    int read;
-    do {
-        read = fread(&byte,1,1,src);
-        if(read == 1) fwrite(&byte,1,1,dst);
-        size += read;
-    } while (read == 1);
+    /* Write contents of data block. */
+    while (1) {
+        if (fread(&data, 1, 1, src)) {
+            if (!fwrite(&data, 1, 1, dst)) {
+                fprintf(stderr, "ERROR: Failed to write block data.\n");
+                exit(EXIT_FAILURE);
+            }
+            size++;
+            checksum += data;
+        } else {
+            break;
+        }
+    }
+    fclose(src);
 
 
-    uint16_t footer[] = {0,1,6,01000,0};
-    fwrite(footer,10,1,dst);
+    /* Now that block size is known, update block header. */
+    if (fseek(dst, 2, SEEK_SET)) {
+        fprintf(stderr, "ERROR: Failed seek back to header of data block.\n");
+        exit(EXIT_FAILURE);
+    }
+    for (int i = 0; i < 2; i++) {
+        switch (i) {
+            case 0: data = size & 0xff;        break;
+            case 1: data = (size >> 8) & 0xff; break;
+        }
+        if (!fwrite(&data, 1, 1, dst)) {
+            fprintf(stderr, "ERROR: Failed to write block size into header.\n");
+            exit(EXIT_FAILURE);
+        }
+        checksum += data; // Header is included in checksum.
+    }
+    if (fseek(dst, 0, SEEK_END)) {
+        fprintf(stderr, "ERROR: Failed seek to end of data block.\n");
+        exit(EXIT_FAILURE);
+    }
 
 
-    fseek(dst,2,SEEK_SET);
-    fwrite(&size,2,1,dst);
+    /* Write checksum for data block. */
+    checksum = (~checksum) + 1;
+    data = checksum & 0xff;
+    if (!fwrite(&data, 1, 1, dst)) {
+        fprintf(stderr, "ERROR: Failed to write checksum.\n");
+        exit(EXIT_FAILURE);
+    }
 
 
-    fclose(src);
+    /* Write empty block to indicate end-of-tape. */
+    for (int i = 0; i < 8; i++) {
+        switch (i) {
+            case 0: data = 0001;                  break;
+            case 1: data = 0000;                  break;
+            case 2: data = 0006;                  break;
+            case 3: data = 0000;                  break;
+            case 4: data = address & 0xff;        break;
+            case 5: data = (address >> 8) & 0xff; break;
+            case 6: data = 0000;                  break;
+            case 7: data = 0000;                  break;
+        }
+        if (!fwrite(&data, 1, 1, dst)) {
+            fprintf(stderr, "ERROR: Failed to write end-of-tape block.\n");
+            exit(EXIT_FAILURE);
+        }
+    }
     fclose(dst);
 }
     fclose(dst);
 }