Implement the add/delete bootloader functionality.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11765 a1c6a512-1295-4272-9138-f99709370657
diff --git a/tools/ipodpatcher/ipodpatcher.c b/tools/ipodpatcher/ipodpatcher.c
index 6fc1ce4..87b181b 100644
--- a/tools/ipodpatcher/ipodpatcher.c
+++ b/tools/ipodpatcher/ipodpatcher.c
@@ -31,7 +31,7 @@
 
 #define VERSION "0.5"
 
-//#define DEBUG
+int verbose = 0;
 
 /* The following string appears at the start of the firmware partition */
 static const char *apple_stop_sign = "{{~~  /-----\\   "\
@@ -337,8 +337,8 @@
     fprintf(stderr,"  -l,  --list\n");
     fprintf(stderr,"  -r,  --read-partition   bootpartition.bin\n");
     fprintf(stderr,"  -w,  --write-partition  bootpartition.bin\n");
-    fprintf(stderr,"  -ef, --extract-firmware filename.ipod\n");
-    fprintf(stderr,"  -rf, --replace-firmware filename.ipod\n");
+    fprintf(stderr,"  -rf, --read-firmware    filename.ipod\n");
+    fprintf(stderr,"  -wf, --write-firmware   filename.ipod\n");
     fprintf(stderr,"  -a,  --add-bootloader   filename.ipod\n");
     fprintf(stderr,"  -d,  --delete-bootloader\n");
     fprintf(stderr,"\n");
@@ -360,10 +360,10 @@
    NONE,
    SHOW_INFO,
    LIST_IMAGES,
-   REMOVE_BOOTLOADER,
-   INSERT_BOOTLOADER,
-   EXTRACT_FIRMWARE,
-   REPLACE_FIRMWARE,
+   DELETE_BOOTLOADER,
+   ADD_BOOTLOADER,
+   READ_FIRMWARE,
+   WRITE_FIRMWARE,
    READ_PARTITION,
    WRITE_PARTITION
 };
@@ -389,16 +389,344 @@
   uint32_t loadAddr;
 };
 
-int remove_bootloader(HANDLE dh, int start, int sector_size, 
-                      struct ipod_directory_t* ipod_directory)
+
+int diskmove(HANDLE dh, int start, int nimages, struct ipod_directory_t* ipod_directory,
+             int sector_size,int delta)
 {
-    fprintf(stderr,"[ERR]  Sorry, not yet implemented.\n");
-    return -1;
+    int src_start;
+    int src_end;
+    int dest_start;
+    int dest_end;
+    int bytesleft;
+    int chunksize;
+    int i;
+    int n;
+
+    src_start = start + ipod_directory[1].devOffset + sector_size;
+    src_end = (start + ipod_directory[nimages-1].devOffset + sector_size + ipod_directory[nimages-1].len + (sector_size-1)) & ~(sector_size-1);
+    bytesleft = src_end - src_start;
+    dest_start = start + src_start + delta;
+    dest_end = start + src_end + delta;
+
+    if (verbose) {
+        fprintf(stderr,"[INFO] Need to move images 2-%d forward %08x bytes\n",nimages,delta);
+        fprintf(stderr,"[VERB] src_start     = %08x\n",src_start);
+        fprintf(stderr,"[VERB] src_end       = %08x\n",src_end);
+        fprintf(stderr,"[VERB] dest_start    = %08x\n",dest_start);
+        fprintf(stderr,"[VERB] dest_end      = %08x\n",dest_end);
+        fprintf(stderr,"[VERB] bytes to copy = %08x\n",bytesleft);
+    }
+
+    while (bytesleft > 0) {
+        if (bytesleft <= BUFFER_SIZE) {
+            chunksize = bytesleft;
+        } else {
+            chunksize = BUFFER_SIZE;
+        }
+
+        if (verbose) {
+            fprintf(stderr,"[VERB] Copying %08x bytes from %08x to %08x\n",
+                           chunksize,
+                           dest_end-chunksize,
+                           dest_end-chunksize+delta);
+        }
+
+
+        if (ipod_seek(dh,dest_end-chunksize) < 0) {
+            fprintf(stderr,"[ERR]  Seek failed\n");
+            return -1;
+        }
+
+        if ((n = ipod_read(dh,sectorbuf,chunksize)) < 0) {
+            perror("[ERR]  Write failed\n");
+            return -1;
+        }
+
+        if (n < chunksize) {
+            fprintf(stderr,"[ERR]  Short read - requested %d bytes, received %d\n",
+                           i,n);
+            return -1;
+        }
+
+        if (ipod_seek(dh,dest_end-chunksize+delta) < 0) {
+            fprintf(stderr,"[ERR]  Seek failed\n");
+            return -1;
+        }
+
+        if ((n = ipod_write(dh,sectorbuf,chunksize)) < 0) {
+            perror("[ERR]  Write failed\n");
+            return -1;
+        }
+
+        if (n < chunksize) {
+            fprintf(stderr,"[ERR]  Short write - requested %d bytes, received %d\n"
+                          ,i,n);
+            return -1;
+        }
+
+        dest_end -= chunksize;
+        bytesleft -= chunksize;
+    }
+
+    return 0;
 }
 
-int replace_firmware(HANDLE dh, char* filename, int start, int sector_size, 
-                     int nimages, struct ipod_directory_t* ipod_directory, 
-                     off_t diroffset, int modelnum, char* modelname)
+int add_bootloader(HANDLE dh, char* filename, int start, int sector_size, 
+                   int nimages, struct ipod_directory_t* ipod_directory, 
+                   off_t diroffset, int modelnum, char* modelname)
+{
+    int length;
+    int i;
+    int n;
+    int infile;
+    int paddedlength;
+    int entryOffset;
+    int delta = 0;
+    unsigned long chksum=0;
+    unsigned long filechksum=0;
+    unsigned char header[8];  /* Header for .ipod file */
+
+    /* First check that the input file is the correct type for this ipod. */
+    infile=open(filename,O_RDONLY);
+    if (infile < 0) {
+        fprintf(stderr,"[ERR]  Couldn't open input file %s\n",filename);
+        return -1;
+    }
+
+    n = read(infile,header,8);
+    if (n < 8) {
+        fprintf(stderr,"[ERR]  Failed to read header from %s\n",filename);
+        close(infile);
+        return -1;
+    }
+
+    if (memcmp(header+4,modelname,4)!=0) {
+        fprintf(stderr,"[ERR]  Model name in input file (%c%c%c%c) doesn't match ipod model (%s)\n",
+                header[4],header[5],header[6],header[7],modelname);
+        close(infile);
+        return -1;
+    }
+
+    filechksum = be2int(header);
+
+    length=filesize(infile)-8;
+    paddedlength=(length+sector_size-1)&~(sector_size-1);
+
+    /* Now read our bootloader - we need to check it before modifying the partition*/
+    n = read(infile,sectorbuf,length);
+    if (n < 0) {
+        fprintf(stderr,"[ERR]  Couldn't read input file\n");
+        close(infile);
+        return -1;
+    }
+
+    /* Calculate and confirm bootloader checksum */
+    chksum = modelnum;
+    for (i = 0; i < length; i++) {
+         /* add 8 unsigned bits but keep a 32 bit sum */
+         chksum += sectorbuf[i];
+    }
+
+    if (chksum == filechksum) {
+        fprintf(stderr,"[INFO] Checksum OK in %s\n",filename);
+    } else {
+        fprintf(stderr,"[ERR]  Checksum in %s failed check\n",filename);
+        return -1;
+    }
+
+    if (ipod_directory[0].entryOffset>0) {
+        /* Keep the same entryOffset */
+        entryOffset = ipod_directory[0].entryOffset;
+    } else {
+        entryOffset = (ipod_directory[0].len+sector_size-1)&~(sector_size-1);
+    }
+
+    if (entryOffset+paddedlength > BUFFER_SIZE) {
+        fprintf(stderr,"[ERR]  Input file too big for buffer\n");
+        close(infile);
+        return -1;
+    }
+
+    if (verbose) {
+        fprintf(stderr,"[VERB] Original firmware begins at 0x%08x\n",ipod_directory[0].devOffset + sector_size);
+        fprintf(stderr,"[VERB] New entryOffset will be 0x%08x\n",entryOffset);
+        fprintf(stderr,"[VERB] End of bootloader will be at 0x%08x\n",entryOffset+paddedlength);
+    }
+
+    /* Check if we have enough space */
+    /* TODO: Check the size of the partition. */
+    if (nimages > 1) {
+        if ((entryOffset+paddedlength) >= ipod_directory[1].devOffset) {
+            fprintf(stderr,"[INFO] Moving images to create room for new firmware...\n");
+            delta = entryOffset+paddedlength-ipod_directory[1].devOffset;
+
+            if (diskmove(dh,start,nimages,ipod_directory,sector_size,delta) < 0) {
+                close(infile);
+                fprintf(stderr,"[ERR]  Image movement failed.\n");
+                return -1;
+            }
+        }
+    }
+
+
+    /* We have moved the partitions, now we can write our bootloader */
+
+    /* Firstly read the original firmware into sectorbuf */
+    fprintf(stderr,"[INFO] Reading original firmware...\n");
+
+    if (ipod_seek(dh,start+sector_size+ipod_directory[0].devOffset) < 0) {
+        fprintf(stderr,"[ERR]  Seek failed\n");
+        return -1;
+    }
+
+    if ((n = ipod_read(dh,sectorbuf,entryOffset)) < 0) {
+        perror("[ERR]  Read failed\n");
+        return -1;
+    }
+
+    if (n < entryOffset) {
+        fprintf(stderr,"[ERR]  Short read - requested %d bytes, received %d\n"
+                      ,i,n);
+        return -1;
+    }
+
+    /* Now read our bootloader - we need to seek back to 8 bytes from start */
+    lseek(infile,8,SEEK_SET);
+    n = read(infile,sectorbuf+entryOffset,length);
+    if (n < 0) {
+        fprintf(stderr,"[ERR]  Couldn't read input file\n");
+        close(infile);
+        return -1;
+    }
+    close(infile);
+
+    /* Calculate new checksum for combined image */
+    chksum = 0;
+    for (i=0;i<entryOffset + length; i++) {
+         chksum += sectorbuf[i];
+    }    
+
+    /* Now write the combined firmware image to the disk */
+
+    if (ipod_seek(dh,start+sector_size+ipod_directory[0].devOffset) < 0) {
+        fprintf(stderr,"[ERR]  Seek failed\n");
+        return -1;
+    }
+
+    if ((n = ipod_write(dh,sectorbuf,entryOffset+paddedlength)) < 0) {
+        perror("[ERR]  Write failed\n");
+        return -1;
+    }
+
+    if (n < (entryOffset+paddedlength)) {
+        fprintf(stderr,"[ERR]  Short read - requested %d bytes, received %d\n"
+                      ,i,n);
+        return -1;
+    }
+
+    fprintf(stderr,"[INFO]  Wrote %d bytes to firmware partition\n",entryOffset+paddedlength);
+
+
+    /* Read directory */
+    if (ipod_seek(dh,start + diroffset) < 0) { return -1; }
+
+    n=ipod_read(dh, sectorbuf, sector_size);
+    if (n < 0) { return -1; }
+
+    /* Update entries for image 0 */
+    int2le(entryOffset+length,sectorbuf+16);
+    int2le(entryOffset,sectorbuf+24);
+    int2le(chksum,sectorbuf+28);
+
+    /* Update devOffset entries for other images, if we have moved them */
+    if (delta > 0) {
+        for (i=1;i<nimages;i++) {
+           int2le(le2int(sectorbuf+i*40+12)+delta,sectorbuf+i*40+12);
+        }
+    }
+
+    /* Write directory */    
+    if (ipod_seek(dh,start + diroffset) < 0) { return -1; }
+    n=ipod_write(dh, sectorbuf, sector_size);
+    if (n < 0) { return -1; }
+
+    return 0;
+}
+
+int delete_bootloader(HANDLE dh, int start, int sector_size, off_t diroffset,
+                      struct ipod_directory_t* ipod_directory)
+{
+    int length;
+    int i;
+    int n;
+    unsigned long chksum=0;   /* 32 bit checksum - Rockbox .ipod style*/
+
+    /* Removing the bootloader involves adjusting the "length",
+       "chksum" and "entryOffset" values in the osos image's directory
+       entry. */
+
+    /* Firstly check we have a bootloader... */
+
+    if (ipod_directory[0].entryOffset == 0) {
+        fprintf(stderr,"[ERR]  No bootloader found.\n");
+        return -1;
+    }
+
+    length = ipod_directory[0].entryOffset;
+
+    /* Read the firmware so we can calculate the checksum */
+    fprintf(stderr,"[INFO] Reading firmware (%d bytes)\n",length);
+
+    if (ipod_seek(dh,start+sector_size+ipod_directory[0].devOffset) < 0) {
+        return -1;
+    }
+
+    i = (length+sector_size-1) & ~(sector_size-1);
+    fprintf(stderr,"[INFO] Padding read from 0x%08x to 0x%08x bytes\n",
+            length,i);
+
+    if ((n = ipod_read(dh,sectorbuf,i)) < 0) {
+        return -1;
+    }
+
+    if (n < i) {
+        fprintf(stderr,"[ERR]  Short read - requested %d bytes, received %d\n",
+                i,n);
+        return -1;
+    }
+
+    chksum = 0;
+    for (i = 0; i < length; i++) {
+         /* add 8 unsigned bits but keep a 32 bit sum */
+         chksum += sectorbuf[i];
+    }
+
+    /* Now write back the updated directory entry */
+
+    fprintf(stderr,"[INFO] Updating firmware checksum\n");
+
+    /* Read directory */
+    if (ipod_seek(dh,start + diroffset) < 0) { return -1; }
+
+    n=ipod_read(dh, sectorbuf, sector_size);
+    if (n < 0) { return -1; }
+
+    /* Update entries for image 0 */
+    int2le(length,sectorbuf+16);
+    int2le(0,sectorbuf+24);
+    int2le(chksum,sectorbuf+28);
+
+    /* Write directory */    
+    if (ipod_seek(dh,start + diroffset) < 0) { return -1; }
+    n=ipod_write(dh, sectorbuf, sector_size);
+    if (n < 0) { return -1; }
+
+    return 0;
+}
+
+int write_firmware(HANDLE dh, char* filename, int start, int sector_size, 
+                   int nimages, struct ipod_directory_t* ipod_directory, 
+                   off_t diroffset, int modelnum, char* modelname)
 {
     int length;
     int i;
@@ -414,11 +742,14 @@
     infile=open(filename,O_RDONLY);
     if (infile < 0) {
         fprintf(stderr,"[ERR]  Couldn't open input file %s\n",filename);
+        return -1;
     }
 
     n = read(infile,header,8);
     if (n < 8) {
         fprintf(stderr,"[ERR]  Failed to read header from %s\n",filename);
+        close(infile);
+        return -1;
     }
 
     if (memcmp(header+4,modelname,4)!=0) {
@@ -523,7 +854,7 @@
     return 0;
 }
 
-int extract_firmware(HANDLE dh, char* filename, int start, int sector_size, 
+int read_firmware(HANDLE dh, char* filename, int start, int sector_size, 
                      struct ipod_directory_t* ipod_directory, 
                      int modelnum, char* modelname)
 {
@@ -661,22 +992,22 @@
 {
     int i;
 
-#ifdef DEBUG
-    printf("    Type         id  devOffset        len       addr entryOffset    chksum       vers   loadAddr   devOffset+len\n");
-    for (i = 0 ; i < nimages; i++) {
-        printf("%d - %s 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",i,
-               ftypename[ipod_directory[i].ftype],
-               ipod_directory[i].id,
-               ipod_directory[i].devOffset,
-               ipod_directory[i].len,
-               ipod_directory[i].addr,
-               ipod_directory[i].entryOffset,
-               ipod_directory[i].chksum,
-               ipod_directory[i].vers,
-               ipod_directory[i].loadAddr,
-               ipod_directory[i].devOffset+sector_size+((ipod_directory[i].len+sector_size-1)&~(sector_size-1)));
+    if (verbose) {
+        printf("    Type         id  devOffset        len       addr entryOffset    chksum       vers   loadAddr   devOffset+len\n");
+        for (i = 0 ; i < nimages; i++) {
+            printf("%d - %s 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",i,
+                   ftypename[ipod_directory[i].ftype],
+                   ipod_directory[i].id,
+                   ipod_directory[i].devOffset,
+                   ipod_directory[i].len,
+                   ipod_directory[i].addr,
+                   ipod_directory[i].entryOffset,
+                   ipod_directory[i].chksum,
+                   ipod_directory[i].vers,
+                   ipod_directory[i].loadAddr,
+                   ipod_directory[i].devOffset+sector_size+((ipod_directory[i].len+sector_size-1)&~(sector_size-1)));
+        }
     }
-#endif
 
     printf("\n");
     printf("Listing firmware partition contents:\n");
@@ -732,7 +1063,8 @@
     fprintf(stderr,"This is free software; see the source for copying conditions.  There is NO\n");
     fprintf(stderr,"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n");
     
-    if (argc < 2) {
+    if ((argc < 2) || (strcmp(argv[1],"-h")==0) || 
+                      (strcmp(argv[1],"--help")==0)) {
         print_usage();
         return 1;
     }
@@ -751,19 +1083,27 @@
         if ((strcmp(argv[i],"-l")==0) || (strcmp(argv[i],"--list")==0)) {
             action = LIST_IMAGES;
             i++;
-        } else if (strcmp(argv[i],"--remove-bootloader")==0) {
-            action = REMOVE_BOOTLOADER;
+        } else if ((strcmp(argv[i],"-d")==0) || 
+                   (strcmp(argv[i],"--delete-bootloader")==0)) {
+            action = DELETE_BOOTLOADER;
             i++;
-        } else if ((strcmp(argv[i],"-ef")==0) || 
-                   (strcmp(argv[i],"--extract-firmware")==0)) {
-            action = EXTRACT_FIRMWARE;
+        } else if ((strcmp(argv[i],"-a")==0) || 
+                   (strcmp(argv[i],"--add-bootloader")==0)) {
+            action = ADD_BOOTLOADER;
             i++;
             if (i == argc) { print_usage(); return 1; }
             filename=argv[i];
             i++;
         } else if ((strcmp(argv[i],"-rf")==0) || 
-                   (strcmp(argv[i],"--replace-firmware")==0)) {
-            action = REPLACE_FIRMWARE;
+                   (strcmp(argv[i],"--read-firmware")==0)) {
+            action = READ_FIRMWARE;
+            i++;
+            if (i == argc) { print_usage(); return 1; }
+            filename=argv[i];
+            i++;
+        } else if ((strcmp(argv[i],"-wf")==0) || 
+                   (strcmp(argv[i],"--write-firmware")==0)) {
+            action = WRITE_FIRMWARE;
             i++;
             if (i == argc) { print_usage(); return 1; }
             filename=argv[i];
@@ -782,6 +1122,10 @@
             if (i == argc) { print_usage(); return 1; }
             filename=argv[i];
             i++;
+        } else if ((strcmp(argv[i],"-v")==0) || 
+                   (strcmp(argv[i],"--verbose")==0)) {
+            verbose++;
+            i++;
         } else {
             print_usage(); return 1;
         }
@@ -868,34 +1212,52 @@
 
     if (action==LIST_IMAGES) {
         list_images(nimages,ipod_directory,sector_size);
-    } else if (action==REMOVE_BOOTLOADER) {
-        if (ipod_directory[0].entryOffset==0) {
-            fprintf(stderr,"[ERR]  No bootloader detected.\n");
-        } else {
-            if (remove_bootloader(dh, pinfo[0].start*sector_size, sector_size, 
-                                  ipod_directory)==0) {
-                fprintf(stderr,"[INFO] Bootloader removed.\n");
-            }
-        }
-    } else if (action==REPLACE_FIRMWARE) {
+    } else if (action==DELETE_BOOTLOADER) {
         if (ipod_reopen_rw(&dh, devicename) < 0) {
             return 5;
         }
 
-        if (replace_firmware(dh, filename,pinfo[0].start*sector_size, 
-                             sector_size, nimages, ipod_directory, diroffset, 
-                             modelnum, modelname)==0) {
-            fprintf(stderr,"[INFO] Firmware replaced with %s.\n",filename);
+        if (ipod_directory[0].entryOffset==0) {
+            fprintf(stderr,"[ERR]  No bootloader detected.\n");
         } else {
-            fprintf(stderr,"[ERR]  --replace-firmware failed.\n");
+            if (delete_bootloader(dh, pinfo[0].start*sector_size, sector_size, 
+                               diroffset, ipod_directory)==0) {
+                fprintf(stderr,"[INFO] Bootloader removed.\n");
+            } else {
+                fprintf(stderr,"[ERR]  --delete-bootloader failed.\n");
+            }
         }
-    } else if (action==EXTRACT_FIRMWARE) {
-        if (extract_firmware(dh, filename,pinfo[0].start*sector_size, 
-                             sector_size, ipod_directory, modelnum, modelname
-                            )==0) {
-            fprintf(stderr,"[INFO] Firmware extracted to %s.\n",filename);
+    } else if (action==ADD_BOOTLOADER) {
+        if (ipod_reopen_rw(&dh, devicename) < 0) {
+            return 5;
+        }
+
+        if (add_bootloader(dh, filename,pinfo[0].start*sector_size, 
+                           sector_size, nimages, ipod_directory, diroffset, 
+                           modelnum, modelname)==0) {
+            fprintf(stderr,"[INFO] Bootloader %s written to device.\n",filename);
         } else {
-            fprintf(stderr,"[ERR]  --extract-firmware failed.\n");
+            fprintf(stderr,"[ERR]  --add-bootloader failed.\n");
+        }
+    } else if (action==WRITE_FIRMWARE) {
+        if (ipod_reopen_rw(&dh, devicename) < 0) {
+            return 5;
+        }
+
+        if (write_firmware(dh, filename,pinfo[0].start*sector_size, 
+                           sector_size, nimages, ipod_directory, diroffset, 
+                           modelnum, modelname)==0) {
+            fprintf(stderr,"[INFO] Firmware %s written to device.\n",filename);
+        } else {
+            fprintf(stderr,"[ERR]  --write-firmware failed.\n");
+        }
+    } else if (action==READ_FIRMWARE) {
+        if (read_firmware(dh, filename,pinfo[0].start*sector_size, 
+                          sector_size, ipod_directory, modelnum, modelname
+                          )==0) {
+            fprintf(stderr,"[INFO] Firmware read to file %s.\n",filename);
+        } else {
+            fprintf(stderr,"[ERR]  --read-firmware failed.\n");
         }
     } else if (action==READ_PARTITION) {
         outfile = open(filename,O_CREAT|O_WRONLY|O_BINARY,S_IREAD|S_IWRITE);