3e489c14c1
Both patchers use the same function name with one being removed when building for rbutil. This gets in the way trying to move the patchers to libraries, and it also results a linking dependency of sansapatcher on ipodpatcher. Renaming the function makes both more self-contained and avoids potential issues if the functions happen to not do the same. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@23568 a1c6a512-1295-4272-9138-f99709370657
2295 lines
66 KiB
C
2295 lines
66 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2006-2007 Dave Chapman
|
|
*
|
|
* 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 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "parttypes.h"
|
|
#include "ipodio.h"
|
|
#include "ipodpatcher.h"
|
|
|
|
#ifdef WITH_BOOTOBJS
|
|
#include "ipod1g2g.h"
|
|
#include "ipod3g.h"
|
|
#include "ipod4g.h"
|
|
#include "ipodmini.h"
|
|
#include "ipodmini2g.h"
|
|
#include "ipodcolor.h"
|
|
#include "ipodnano.h"
|
|
#include "ipodvideo.h"
|
|
#include "ipodnano2g.h"
|
|
#endif
|
|
|
|
#ifndef RBUTIL
|
|
#include "arc4.h"
|
|
#endif
|
|
|
|
int ipod_verbose = 0;
|
|
|
|
unsigned char* ipod_sectorbuf = NULL;
|
|
|
|
/* The following string appears at the start of the firmware partition */
|
|
static const char *apple_stop_sign = "{{~~ /-----\\ "\
|
|
"{{~~ / \\ "\
|
|
"{{~~| | "\
|
|
"{{~~| S T O P | "\
|
|
"{{~~| | "\
|
|
"{{~~ \\ / "\
|
|
"{{~~ \\-----/ "\
|
|
"Copyright(C) 200"\
|
|
"1 Apple Computer"\
|
|
", Inc.----------"\
|
|
"----------------"\
|
|
"----------------"\
|
|
"----------------"\
|
|
"----------------"\
|
|
"----------------"\
|
|
"---------------";
|
|
|
|
/* Windows requires the buffer for disk I/O to be aligned in memory on a
|
|
multiple of the disk volume size - so we use a single global variable
|
|
and initialise it with ipod_alloc_buf()
|
|
*/
|
|
|
|
char* get_parttype(int pt)
|
|
{
|
|
int i;
|
|
static char unknown[]="Unknown";
|
|
|
|
if (pt == PARTTYPE_HFS) {
|
|
return "HFS/HFS+";
|
|
}
|
|
|
|
i=0;
|
|
while (parttypes[i].name != NULL) {
|
|
if (parttypes[i].type == pt) {
|
|
return (parttypes[i].name);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return unknown;
|
|
}
|
|
|
|
off_t filesize(int fd) {
|
|
struct stat buf;
|
|
|
|
if (fstat(fd,&buf) < 0) {
|
|
perror("[ERR] Checking filesize of input file");
|
|
return -1;
|
|
} else {
|
|
return(buf.st_size);
|
|
}
|
|
}
|
|
|
|
/* Partition table parsing code taken from Rockbox */
|
|
|
|
#define MAX_SECTOR_SIZE 2048
|
|
#define SECTOR_SIZE 512
|
|
|
|
static inline unsigned short le2ushort(unsigned char* buf)
|
|
{
|
|
unsigned short res = (buf[1] << 8) | buf[0];
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline int le2int(unsigned char* buf)
|
|
{
|
|
int32_t res = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline int be2int(unsigned char* buf)
|
|
{
|
|
int32_t res = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline int getint16le(char* buf)
|
|
{
|
|
int16_t res = (buf[1] << 8) | buf[0];
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline void short2le(unsigned short val, unsigned char* addr)
|
|
{
|
|
addr[0] = val & 0xFF;
|
|
addr[1] = (val >> 8) & 0xff;
|
|
}
|
|
|
|
static inline void int2le(unsigned int val, unsigned char* addr)
|
|
{
|
|
addr[0] = val & 0xFF;
|
|
addr[1] = (val >> 8) & 0xff;
|
|
addr[2] = (val >> 16) & 0xff;
|
|
addr[3] = (val >> 24) & 0xff;
|
|
}
|
|
|
|
static inline void int2be(unsigned int val, unsigned char* addr)
|
|
{
|
|
addr[0] = (val >> 24) & 0xff;
|
|
addr[1] = (val >> 16) & 0xff;
|
|
addr[2] = (val >> 8) & 0xff;
|
|
addr[3] = val & 0xFF;
|
|
}
|
|
|
|
|
|
#define BYTES2INT32(array,pos)\
|
|
((long)array[pos] | ((long)array[pos+1] << 8 ) |\
|
|
((long)array[pos+2] << 16 ) | ((long)array[pos+3] << 24 ))
|
|
|
|
int read_partinfo(struct ipod_t* ipod, int silent)
|
|
{
|
|
int i;
|
|
unsigned long count;
|
|
|
|
count = ipod_read(ipod,ipod_sectorbuf, ipod->sector_size);
|
|
|
|
if (count <= 0) {
|
|
ipod_print_error(" Error reading from disk: ");
|
|
return -1;
|
|
}
|
|
|
|
memset(ipod->pinfo, 0, sizeof(ipod->pinfo));
|
|
|
|
if ((ipod_sectorbuf[510] == 0x55) && (ipod_sectorbuf[511] == 0xaa)) {
|
|
/* DOS partition table */
|
|
ipod->macpod = 0;
|
|
/* parse partitions */
|
|
for ( i = 0; i < 4; i++ ) {
|
|
unsigned char* ptr = ipod_sectorbuf + 0x1be + 16*i;
|
|
ipod->pinfo[i].type = ptr[4];
|
|
ipod->pinfo[i].start = BYTES2INT32(ptr, 8);
|
|
ipod->pinfo[i].size = BYTES2INT32(ptr, 12);
|
|
|
|
/* extended? */
|
|
if ( ipod->pinfo[i].type == 5 ) {
|
|
/* not handled yet */
|
|
}
|
|
}
|
|
} else if ((ipod_sectorbuf[0] == 'E') && (ipod_sectorbuf[1] == 'R')) {
|
|
/* Apple Partition Map */
|
|
|
|
/* APM parsing code based on the check_mac_partitions() function in
|
|
ipodloader2 - written by Thomas Tempelmann and released
|
|
under the GPL. */
|
|
|
|
int blkNo = 1;
|
|
int partBlkCount = 1;
|
|
int partBlkSizMul = ipod_sectorbuf[2] / 2;
|
|
|
|
int pmMapBlkCnt; /* # of blks in partition map */
|
|
int pmPyPartStart; /* physical start blk of partition */
|
|
int pmPartBlkCnt; /* # of blks in this partition */
|
|
int i = 0;
|
|
|
|
ipod->macpod = 1;
|
|
|
|
memset(ipod->pinfo,0,sizeof(ipod->pinfo));
|
|
|
|
while (blkNo <= partBlkCount) {
|
|
if (ipod_seek(ipod, blkNo * partBlkSizMul * 512) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed whilst reading APM\n");
|
|
return -1;
|
|
}
|
|
|
|
count = ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
|
|
if (count <= 0) {
|
|
ipod_print_error(" Error reading from disk: ");
|
|
return -1;
|
|
}
|
|
|
|
/* see if it's a partition entry */
|
|
if ((ipod_sectorbuf[0] != 'P') || (ipod_sectorbuf[1] != 'M')) {
|
|
/* end of partition table -> leave the loop */
|
|
break;
|
|
}
|
|
|
|
/* Extract the interesting entries */
|
|
pmMapBlkCnt = be2int(ipod_sectorbuf + 4);
|
|
pmPyPartStart = be2int(ipod_sectorbuf + 8);
|
|
pmPartBlkCnt = be2int(ipod_sectorbuf + 12);
|
|
|
|
/* update the number of part map blocks */
|
|
partBlkCount = pmMapBlkCnt;
|
|
|
|
if (strncmp((char*)(ipod_sectorbuf + 48), "Apple_MDFW", 32)==0) {
|
|
/* A Firmware partition */
|
|
ipod->pinfo[i].start = pmPyPartStart;
|
|
ipod->pinfo[i].size = pmPartBlkCnt;
|
|
ipod->pinfo[i].type = 0;
|
|
i++;
|
|
} else if (strncmp((char*)(ipod_sectorbuf + 48), "Apple_HFS", 32)==0) {
|
|
/* A HFS partition */
|
|
ipod->pinfo[i].start = pmPyPartStart;
|
|
ipod->pinfo[i].size = pmPartBlkCnt;
|
|
ipod->pinfo[i].type = PARTTYPE_HFS;
|
|
i++;
|
|
}
|
|
|
|
blkNo++; /* read next partition map entry */
|
|
}
|
|
} else {
|
|
if (!silent) fprintf(stderr,"[ERR] Bad boot sector signature\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Check that the partition table looks like an ipod:
|
|
1) Partition 1 is of type 0 (Empty) but isn't empty.
|
|
2) Partition 2 is of type 0xb or 0xc (winpod) or -1 (macpod)
|
|
*/
|
|
if ((ipod->pinfo[0].type != 0) || (ipod->pinfo[0].size == 0) ||
|
|
((ipod->pinfo[1].type != 0xb) && (ipod->pinfo[1].type != 0xc) &&
|
|
(ipod->pinfo[1].type != PARTTYPE_HFS))) {
|
|
if (!silent) fprintf(stderr,"[ERR] Partition layout is not an ipod\n");
|
|
return -1;
|
|
}
|
|
|
|
ipod->start = ipod->pinfo[0].start*ipod->sector_size;
|
|
return 0;
|
|
}
|
|
|
|
int read_partition(struct ipod_t* ipod, int outfile)
|
|
{
|
|
int res;
|
|
ssize_t n;
|
|
int bytesleft;
|
|
int chunksize;
|
|
int count = ipod->pinfo[0].size;
|
|
|
|
if (ipod_seek(ipod, ipod->start) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr,"[INFO] Writing %d sectors to output file\n",count);
|
|
|
|
bytesleft = count * ipod->sector_size;
|
|
while (bytesleft > 0) {
|
|
if (bytesleft > BUFFER_SIZE) {
|
|
chunksize = BUFFER_SIZE;
|
|
} else {
|
|
chunksize = bytesleft;
|
|
}
|
|
|
|
n = ipod_read(ipod, ipod_sectorbuf, chunksize);
|
|
|
|
if (n < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (n < chunksize) {
|
|
fprintf(stderr,
|
|
"[ERR] Short read in disk_read() - requested %d, got %d\n",
|
|
chunksize,(int)n);
|
|
return -1;
|
|
}
|
|
|
|
bytesleft -= n;
|
|
|
|
res = write(outfile,ipod_sectorbuf,n);
|
|
|
|
if (res < 0) {
|
|
perror("[ERR] write in disk_read");
|
|
return -1;
|
|
}
|
|
|
|
if (res != n) {
|
|
fprintf(stderr,
|
|
"Short write - requested %d, received %d - aborting.\n",(int)n,res);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr,"[INFO] Done.\n");
|
|
return 0;
|
|
}
|
|
|
|
int write_partition(struct ipod_t* ipod, int infile)
|
|
{
|
|
ssize_t res;
|
|
int n;
|
|
int bytesread;
|
|
int byteswritten = 0;
|
|
int eof;
|
|
int padding = 0;
|
|
|
|
if (ipod_seek(ipod, ipod->start) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr,"[INFO] Writing input file to device\n");
|
|
bytesread = 0;
|
|
eof = 0;
|
|
while (!eof) {
|
|
n = read(infile,ipod_sectorbuf,BUFFER_SIZE);
|
|
|
|
if (n < 0) {
|
|
perror("[ERR] read in disk_write");
|
|
return -1;
|
|
}
|
|
|
|
if (n < BUFFER_SIZE) {
|
|
eof = 1;
|
|
/* We need to pad the last write to a multiple of SECTOR_SIZE */
|
|
if ((n % ipod->sector_size) != 0) {
|
|
padding = (ipod->sector_size-(n % ipod->sector_size));
|
|
n += padding;
|
|
}
|
|
}
|
|
|
|
bytesread += n;
|
|
|
|
res = ipod_write(ipod, ipod_sectorbuf, n);
|
|
|
|
if (res < 0) {
|
|
ipod_print_error(" Error writing to disk: ");
|
|
fprintf(stderr,"Bytes written: %d\n",byteswritten);
|
|
return -1;
|
|
}
|
|
|
|
if (res != n) {
|
|
fprintf(stderr,"[ERR] Short write - requested %d, received %d - aborting.\n",n,(int)res);
|
|
return -1;
|
|
}
|
|
|
|
byteswritten += res;
|
|
}
|
|
|
|
fprintf(stderr,"[INFO] Wrote %d bytes plus %d bytes padding.\n",
|
|
byteswritten-padding,padding);
|
|
return 0;
|
|
}
|
|
|
|
char* ftypename[] = { "OSOS", "RSRC", "AUPD", "HIBE", "OSBK" };
|
|
|
|
int diskmove(struct ipod_t* ipod, int delta)
|
|
{
|
|
int src_start;
|
|
int src_end;
|
|
int bytesleft;
|
|
int chunksize;
|
|
int n;
|
|
|
|
src_start = ipod->ipod_directory[1].devOffset;
|
|
src_end = (ipod->ipod_directory[ipod->nimages-1].devOffset + ipod->sector_size +
|
|
ipod->ipod_directory[ipod->nimages-1].len +
|
|
(ipod->sector_size-1)) & ~(ipod->sector_size-1);
|
|
bytesleft = src_end - src_start;
|
|
|
|
if (ipod_verbose) {
|
|
fprintf(stderr,"[INFO] Need to move images 2-%d forward %08x bytes\n", ipod->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",src_start+delta);
|
|
fprintf(stderr,"[VERB] dest_end = %08x\n",src_end+delta);
|
|
fprintf(stderr,"[VERB] bytes to copy = %08x\n",bytesleft);
|
|
}
|
|
|
|
while (bytesleft > 0) {
|
|
if (bytesleft <= BUFFER_SIZE) {
|
|
chunksize = bytesleft;
|
|
} else {
|
|
chunksize = BUFFER_SIZE;
|
|
}
|
|
|
|
if (ipod_verbose) {
|
|
fprintf(stderr,"[VERB] Copying %08x bytes from %08x to %08x (absolute %08x to %08x)\n",
|
|
chunksize,
|
|
src_end-chunksize,
|
|
src_end-chunksize+delta,
|
|
(unsigned int)(ipod->start+src_end-chunksize),
|
|
(unsigned int)(ipod->start+src_end-chunksize+delta));
|
|
}
|
|
|
|
|
|
if (ipod_seek(ipod, ipod->start+src_end-chunksize) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_read(ipod,ipod_sectorbuf,chunksize)) < 0) {
|
|
perror("[ERR] Write failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (n < chunksize) {
|
|
fprintf(stderr,"[ERR] Short read - requested %d bytes, received %d\n",
|
|
chunksize,n);
|
|
return -1;
|
|
}
|
|
|
|
if (ipod_seek(ipod, ipod->start+src_end-chunksize+delta) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_write(ipod,ipod_sectorbuf,chunksize)) < 0) {
|
|
perror("[ERR] Write failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (n < chunksize) {
|
|
fprintf(stderr,"[ERR] Short write - requested %d bytes, received %d\n"
|
|
,chunksize,n);
|
|
return -1;
|
|
}
|
|
|
|
src_end -= chunksize;
|
|
bytesleft -= chunksize;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rename_image(struct ipod_t* ipod, char* from, char* to)
|
|
{
|
|
int n;
|
|
int x;
|
|
int found;
|
|
int i;
|
|
unsigned char* p;
|
|
|
|
/* diroffset may not be sector-aligned */
|
|
x = ipod->diroffset % ipod->sector_size;
|
|
|
|
/* Read directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) {
|
|
fprintf(stderr,"[ERR] Seek to diroffset (%08x) failed.\n",(unsigned)ipod->diroffset);
|
|
return -1;
|
|
}
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Read of directory failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
p = ipod_sectorbuf + x;
|
|
|
|
/* A hack to detect 2nd gen Nanos - maybe there is a better way? */
|
|
if (p[0] == 0)
|
|
{
|
|
/* Adjust diroffset */
|
|
ipod->diroffset += ipod->sector_size - x;
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Read of directory failed.\n");
|
|
return -1;
|
|
}
|
|
p = ipod_sectorbuf;
|
|
}
|
|
|
|
found = 0;
|
|
for (i=0 ; !found && i < MAX_IMAGES; i++) {
|
|
if (memcmp(p + 4, from, 4) == 0) {
|
|
memcpy(p + 4, to, 4);
|
|
|
|
found = 1;
|
|
}
|
|
p += 40;
|
|
}
|
|
|
|
if (!found) {
|
|
fprintf(stderr,"[ERR] Unexpected error - no \"%s\" image!\n", from);
|
|
return -1;
|
|
}
|
|
|
|
/* Write directory back to disk */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) {
|
|
fprintf(stderr,"[ERR] Seek to diroffset (%08x) failed.\n",(unsigned)ipod->diroffset);
|
|
return -1;
|
|
}
|
|
|
|
n=ipod_write(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Write of directory failed in rename_image.\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int delete_image(struct ipod_t* ipod, char* name)
|
|
{
|
|
int n;
|
|
int x;
|
|
int found;
|
|
int i;
|
|
unsigned char* p;
|
|
|
|
/* diroffset may not be sector-aligned */
|
|
x = ipod->diroffset % ipod->sector_size;
|
|
|
|
/* Read directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) {
|
|
fprintf(stderr,"[ERR] Seek to diroffset (%08x) failed.\n",(unsigned)ipod->diroffset);
|
|
return -1;
|
|
}
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Read of directory failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
p = ipod_sectorbuf + x;
|
|
|
|
/* A hack to detect 2nd gen Nanos - maybe there is a better way? */
|
|
if (p[0] == 0)
|
|
{
|
|
/* Adjust diroffset */
|
|
ipod->diroffset += ipod->sector_size - x;
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Read of directory failed.\n");
|
|
return -1;
|
|
}
|
|
p = ipod_sectorbuf;
|
|
}
|
|
|
|
found = 0;
|
|
for (i=0 ; !found && i < MAX_IMAGES; i++) {
|
|
if (memcmp(p + 4, name, 4) == 0) {
|
|
memset(p, 0, 40); /* Delete directory entry */
|
|
found = 1;
|
|
}
|
|
p += 40;
|
|
}
|
|
|
|
if (!found) {
|
|
fprintf(stderr,"[ERR] Unexpected error - no \"%s\" image!\n", name);
|
|
return -1;
|
|
}
|
|
|
|
/* Write directory back to disk */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) {
|
|
fprintf(stderr,"[ERR] Seek to diroffset (%08x) failed.\n",(unsigned)ipod->diroffset);
|
|
return -1;
|
|
}
|
|
|
|
n=ipod_write(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Write of directory failed in delete_image.\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int add_new_image(struct ipod_t* ipod, char* imagename, char* filename, int type)
|
|
{
|
|
int length;
|
|
int found;
|
|
int i;
|
|
int x;
|
|
int n;
|
|
int infile;
|
|
int newsize;
|
|
unsigned long chksum=0;
|
|
unsigned long filechksum=0;
|
|
unsigned long offset;
|
|
unsigned char header[8]; /* Header for .ipod file */
|
|
unsigned char* p;
|
|
|
|
#ifdef WITH_BOOTOBJS
|
|
if (type == FILETYPE_INTERNAL) {
|
|
fprintf(stderr,"[INFO] Using internal bootloader - %d bytes\n",ipod->bootloader_len);
|
|
length = ipod->bootloader_len;
|
|
infile = -1;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
if (type==FILETYPE_DOT_IPOD) {
|
|
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, ipod->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], ipod->modelname);
|
|
close(infile);
|
|
return -1;
|
|
}
|
|
|
|
filechksum = be2int(header);
|
|
|
|
length = filesize(infile)-8;
|
|
} else {
|
|
length = filesize(infile);
|
|
}
|
|
}
|
|
|
|
newsize=(length+ipod->sector_size-1)&~(ipod->sector_size-1);
|
|
|
|
fprintf(stderr,"[INFO] Padding input file from 0x%08x to 0x%08x bytes\n",
|
|
length,newsize);
|
|
|
|
if (newsize > BUFFER_SIZE) {
|
|
fprintf(stderr,"[ERR] Input file too big for buffer\n");
|
|
if (infile >= 0) close(infile);
|
|
return -1;
|
|
}
|
|
|
|
/* TODO: Check if we have enough space in the partition for the new image */
|
|
|
|
#ifdef WITH_BOOTOBJS
|
|
if (type == FILETYPE_INTERNAL) {
|
|
memcpy(ipod_sectorbuf,ipod->bootloader,ipod->bootloader_len);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
fprintf(stderr,"[INFO] Reading input file...\n");
|
|
|
|
n = read(infile,ipod_sectorbuf,length);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Couldn't read input file\n");
|
|
close(infile);
|
|
return -1;
|
|
}
|
|
close(infile);
|
|
}
|
|
|
|
/* Pad the data with zeros */
|
|
memset(ipod_sectorbuf+length,0,newsize-length);
|
|
|
|
if (type==FILETYPE_DOT_IPOD) {
|
|
chksum = ipod->modelnum;
|
|
for (i = 0; i < length; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
chksum += ipod_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;
|
|
}
|
|
}
|
|
|
|
|
|
offset = ipod->fwoffset + ipod->ipod_directory[ipod->nimages - 1].devOffset +
|
|
ipod->ipod_directory[ipod->nimages - 1].len + ipod->sector_size;
|
|
|
|
/* 2nd Gen Nano has encrypted firmware, and the sector
|
|
preceeding the firmware contains hashes that need to be
|
|
preserved. Nano 2G images include these extra 2048 (0x800)
|
|
bytes
|
|
*/
|
|
if (ipod_seek(ipod, offset - (ipod->modelnum == 62 ? 0x800 : 0)) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_write(ipod,ipod_sectorbuf,newsize)) < 0) {
|
|
perror("[ERR] Write failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (n < newsize) {
|
|
fprintf(stderr,"[ERR] Short write - requested %d bytes, received %d\n"
|
|
,newsize,n);
|
|
return -1;
|
|
}
|
|
fprintf(stderr,"[INFO] Wrote %d bytes to firmware partition\n",n);
|
|
|
|
/* Now we need to create a new directory entry
|
|
|
|
NOTE: On the Nano 2G, the checksum is the checksum of the
|
|
unencrypted firmware. But this isn't checked by the NOR
|
|
bootloader (there are cryptographic hashes in the
|
|
firmware itself), so it doesn't matter that this is
|
|
wrong.
|
|
*/
|
|
chksum = 0;
|
|
for (i = 0; i < length; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
chksum += ipod_sectorbuf[i];
|
|
}
|
|
|
|
x = ipod->diroffset % ipod->sector_size;
|
|
|
|
/* Read directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) { return -1; }
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) { return -1; }
|
|
|
|
/* Create a new directory entry */
|
|
|
|
/* Copy OSOS or OSBK details - we assume one of them exists */
|
|
p = ipod_sectorbuf + x;
|
|
found = 0;
|
|
for (i = 0; !found && i < ipod->nimages; i++) {
|
|
if ((memcmp(p + 4, "soso", 4)==0) || (memcmp(p + 4, "kbso", 4)==0)) {
|
|
found = 1;
|
|
} else {
|
|
p += 40;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
fprintf(stderr,"[ERR] No OSOS or OSBK image to copy directory from\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Copy directory image */
|
|
memcpy(ipod_sectorbuf + x + (ipod->nimages * 40), p, 40);
|
|
p = ipod_sectorbuf + x + (ipod->nimages * 40);
|
|
|
|
/* Modify directory. */
|
|
memcpy(p + 4, imagename, 4);
|
|
int2le(offset - ipod->fwoffset, p + 12); /* devOffset */
|
|
int2le(length - (ipod->modelnum==62 ? 0x800: 0), p + 16); /* len */
|
|
int2le(chksum, p + 28); /* checksum */
|
|
|
|
/* Write directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) { return -1; }
|
|
n=ipod_write(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) { return -1; }
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
Bootloader installation on the Nano2G consists of renaming the
|
|
OSOS image to OSBK and then writing the Rockbox bootloader as a
|
|
new OSOS image.
|
|
|
|
Maybe this approach can/should be adapted for other ipods, as it
|
|
prevents the Apple bootloader loading the original firmware into
|
|
RAM along with the Rockbox bootloader (and hence will give a
|
|
faster boot when the user just wants to start Rockbox).
|
|
|
|
*/
|
|
|
|
static int add_bootloader_nano2g(struct ipod_t* ipod, char* filename, int type)
|
|
{
|
|
int i;
|
|
int has_osbk = 0;
|
|
|
|
/* Check if we already have an OSBK image */
|
|
for (i = 0; i < ipod->nimages; i++) {
|
|
if (ipod->ipod_directory[i].ftype==FTYPE_OSBK) {
|
|
has_osbk = 1;
|
|
}
|
|
}
|
|
|
|
if (has_osbk == 0) {
|
|
/* First-time install - rename OSOS to OSBK and create new OSOS for bootloader */
|
|
fprintf(stderr,"[INFO] Creating OSBK backup image of original firmware\n");
|
|
|
|
if (rename_image(ipod, "soso", "kbso") < 0) {
|
|
fprintf(stderr,"[ERR] Could not rename OSOS image\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Add our bootloader as a brand new image */
|
|
return add_new_image(ipod, "soso", filename, type);
|
|
} else {
|
|
/* This is an update, just replace OSOS with our bootloader */
|
|
|
|
return write_firmware(ipod, filename, type);
|
|
}
|
|
}
|
|
|
|
|
|
static int delete_bootloader_nano2g(struct ipod_t* ipod)
|
|
{
|
|
int i;
|
|
int has_osbk = 0;
|
|
|
|
/* Check if we have an OSBK image */
|
|
for (i = 0; i < ipod->nimages; i++) {
|
|
if (ipod->ipod_directory[i].ftype==FTYPE_OSBK) {
|
|
has_osbk = 1;
|
|
}
|
|
}
|
|
|
|
if (has_osbk == 0) {
|
|
fprintf(stderr,"[ERR] No OSBK image found - nothing to uninstall\n");
|
|
return -1;
|
|
} else {
|
|
/* Delete our bootloader image */
|
|
if (delete_image(ipod, "soso") < 0) {
|
|
fprintf(stderr,"[WARN] Could not delete OSOS image\n");
|
|
} else {
|
|
fprintf(stderr,"[INFO] OSOS image deleted\n");
|
|
}
|
|
|
|
if (rename_image(ipod, "kbso", "soso") < 0) {
|
|
fprintf(stderr,"[ERR] Could not rename OSBK image\n");
|
|
return -1;
|
|
}
|
|
|
|
|
|
fprintf(stderr,"[INFO] OSBK image renamed to OSOS - bootloader uninstalled.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
int add_bootloader(struct ipod_t* ipod, char* filename, int type)
|
|
{
|
|
int length;
|
|
int i;
|
|
int x;
|
|
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 */
|
|
unsigned char* bootloader_buf;
|
|
|
|
/* The 2nd gen Nano is installed differently */
|
|
if (ipod->modelnum == 62) {
|
|
return add_bootloader_nano2g(ipod, filename, type);
|
|
}
|
|
|
|
/* Calculate the position in the OSOS image where our bootloader will go. */
|
|
if (ipod->ipod_directory[0].entryOffset>0) {
|
|
/* Keep the same entryOffset */
|
|
entryOffset = ipod->ipod_directory[0].entryOffset;
|
|
} else {
|
|
entryOffset = (ipod->ipod_directory[0].len+ipod->sector_size-1)&~(ipod->sector_size-1);
|
|
}
|
|
|
|
#ifdef WITH_BOOTOBJS
|
|
if (type == FILETYPE_INTERNAL) {
|
|
fprintf(stderr,"[INFO] Using internal bootloader - %d bytes\n",ipod->bootloader_len);
|
|
memcpy(ipod_sectorbuf+entryOffset,ipod->bootloader,ipod->bootloader_len);
|
|
length = ipod->bootloader_len;
|
|
paddedlength=(ipod->bootloader_len+ipod->sector_size-1)&~(ipod->sector_size-1);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
infile=open(filename,O_RDONLY);
|
|
if (infile < 0) {
|
|
fprintf(stderr,"[ERR] Couldn't open input file %s\n",filename);
|
|
return -1;
|
|
}
|
|
|
|
if (type==FILETYPE_DOT_IPOD) {
|
|
/* First check that the input file is the correct type for this ipod. */
|
|
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, ipod->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], ipod->modelname);
|
|
close(infile);
|
|
return -1;
|
|
}
|
|
|
|
filechksum = be2int(header);
|
|
|
|
length=filesize(infile)-8;
|
|
} else {
|
|
length=filesize(infile);
|
|
}
|
|
paddedlength=(length+ipod->sector_size-1)&~(ipod->sector_size-1);
|
|
|
|
bootloader_buf = malloc(length);
|
|
if (bootloader_buf == NULL) {
|
|
fprintf(stderr,"[ERR] Can not allocate memory for bootloader\n");
|
|
return -1;
|
|
}
|
|
/* Now read our bootloader - we need to check it before modifying the partition*/
|
|
n = read(infile,bootloader_buf,length);
|
|
close(infile);
|
|
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Couldn't read input file\n");
|
|
return -1;
|
|
}
|
|
|
|
if (type==FILETYPE_DOT_IPOD) {
|
|
/* Calculate and confirm bootloader checksum */
|
|
chksum = ipod->modelnum;
|
|
for (i = 0; i < length; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
chksum += bootloader_buf[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 (entryOffset+paddedlength > BUFFER_SIZE) {
|
|
fprintf(stderr,"[ERR] Input file too big for buffer\n");
|
|
return -1;
|
|
}
|
|
|
|
if (ipod_verbose) {
|
|
fprintf(stderr,"[VERB] Original firmware begins at 0x%08x\n", ipod->ipod_directory[0].devOffset + ipod->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 (ipod->nimages > 1) {
|
|
if ((ipod->ipod_directory[0].devOffset+entryOffset+paddedlength) >
|
|
ipod->ipod_directory[1].devOffset) {
|
|
fprintf(stderr,"[INFO] Moving images to create room for new firmware...\n");
|
|
delta = ipod->ipod_directory[0].devOffset + entryOffset+paddedlength
|
|
- ipod->ipod_directory[1].devOffset + ipod->sector_size;
|
|
|
|
if (diskmove(ipod, delta) < 0) {
|
|
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 ipod_sectorbuf */
|
|
fprintf(stderr,"[INFO] Reading original firmware...\n");
|
|
if (ipod_seek(ipod, ipod->fwoffset+ipod->ipod_directory[0].devOffset) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_read(ipod,ipod_sectorbuf,entryOffset)) < 0) {
|
|
perror("[ERR] Read failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (n < entryOffset) {
|
|
fprintf(stderr,"[ERR] Short read - requested %d bytes, received %d\n"
|
|
,entryOffset,n);
|
|
return -1;
|
|
}
|
|
|
|
#ifdef WITH_BOOTOBJS
|
|
if (type == FILETYPE_INTERNAL) {
|
|
memcpy(ipod_sectorbuf+entryOffset,ipod->bootloader,ipod->bootloader_len);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
memcpy(ipod_sectorbuf+entryOffset,bootloader_buf,length);
|
|
free(bootloader_buf);
|
|
}
|
|
|
|
/* Calculate new checksum for combined image */
|
|
chksum = 0;
|
|
for (i=0;i<entryOffset + length; i++) {
|
|
chksum += ipod_sectorbuf[i];
|
|
}
|
|
|
|
/* Now write the combined firmware image to the disk */
|
|
|
|
if (ipod_seek(ipod, ipod->fwoffset+ipod->ipod_directory[0].devOffset) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_write(ipod,ipod_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"
|
|
,entryOffset+paddedlength,n);
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr,"[INFO] Wrote %d bytes to firmware partition\n",entryOffset+paddedlength);
|
|
|
|
x = ipod->diroffset % ipod->sector_size;
|
|
|
|
/* Read directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed\n");
|
|
return -1;
|
|
}
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Directory read failed\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Update entries for image 0 */
|
|
int2le(entryOffset+length,ipod_sectorbuf+x+16);
|
|
int2le(entryOffset,ipod_sectorbuf+x+24);
|
|
int2le(chksum,ipod_sectorbuf+x+28);
|
|
int2le(0xffffffff,ipod_sectorbuf+x+36); /* loadAddr */
|
|
|
|
/* Update devOffset entries for other images, if we have moved them */
|
|
if (delta > 0) {
|
|
for (i=1;i<ipod->nimages;i++) {
|
|
int2le(le2int(ipod_sectorbuf+x+i*40+12)+delta,ipod_sectorbuf+x+i*40+12);
|
|
}
|
|
}
|
|
|
|
/* Write directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) {
|
|
fprintf(stderr,"[ERR] Seek to %d failed\n", (int)(ipod->start+ipod->diroffset-x));
|
|
return -1;
|
|
}
|
|
n=ipod_write(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Directory write failed\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int delete_bootloader(struct ipod_t* ipod)
|
|
{
|
|
int length;
|
|
int i;
|
|
int x;
|
|
int n;
|
|
unsigned long chksum=0; /* 32 bit checksum - Rockbox .ipod style*/
|
|
|
|
/* The 2nd gen Nano is installed differently */
|
|
if (ipod->modelnum == 62) {
|
|
return delete_bootloader_nano2g(ipod);
|
|
}
|
|
|
|
/* 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->ipod_directory[0].entryOffset == 0) {
|
|
fprintf(stderr,"[ERR] No bootloader found.\n");
|
|
return -1;
|
|
}
|
|
|
|
length = ipod->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(ipod, ipod->fwoffset+ipod->ipod_directory[0].devOffset) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
i = (length+ipod->sector_size-1) & ~(ipod->sector_size-1);
|
|
fprintf(stderr,"[INFO] Padding read from 0x%08x to 0x%08x bytes\n",
|
|
length,i);
|
|
|
|
if ((n = ipod_read(ipod,ipod_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 += ipod_sectorbuf[i];
|
|
}
|
|
|
|
/* Now write back the updated directory entry */
|
|
|
|
fprintf(stderr,"[INFO] Updating firmware checksum\n");
|
|
|
|
x = ipod->diroffset % ipod->sector_size;
|
|
|
|
/* Read directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) { return -1; }
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) { return -1; }
|
|
|
|
/* Update entries for image 0 */
|
|
int2le(length,ipod_sectorbuf+x+16);
|
|
int2le(0,ipod_sectorbuf+x+24);
|
|
int2le(chksum,ipod_sectorbuf+x+28);
|
|
|
|
/* Write directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) { return -1; }
|
|
n=ipod_write(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) { return -1; }
|
|
|
|
return 0;
|
|
}
|
|
|
|
int write_firmware(struct ipod_t* ipod, char* filename, int type)
|
|
{
|
|
int length;
|
|
int i;
|
|
int x;
|
|
int n;
|
|
int infile;
|
|
int newsize;
|
|
int bytesavailable;
|
|
unsigned long chksum=0;
|
|
unsigned long filechksum=0;
|
|
unsigned long offset;
|
|
unsigned char header[8]; /* Header for .ipod file */
|
|
unsigned char* p;
|
|
|
|
#ifdef WITH_BOOTOBJS
|
|
if (type == FILETYPE_INTERNAL) {
|
|
fprintf(stderr,"[INFO] Using internal bootloader - %d bytes\n",ipod->bootloader_len);
|
|
length = ipod->bootloader_len;
|
|
infile = -1;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
if (type==FILETYPE_DOT_IPOD) {
|
|
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, ipod->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], ipod->modelname);
|
|
close(infile);
|
|
return -1;
|
|
}
|
|
|
|
filechksum = be2int(header);
|
|
|
|
length = filesize(infile)-8;
|
|
} else {
|
|
length = filesize(infile);
|
|
}
|
|
}
|
|
|
|
newsize=(length+ipod->sector_size-1)&~(ipod->sector_size-1);
|
|
|
|
fprintf(stderr,"[INFO] Padding input file from 0x%08x to 0x%08x bytes\n",
|
|
length,newsize);
|
|
|
|
if (newsize > BUFFER_SIZE) {
|
|
fprintf(stderr,"[ERR] Input file too big for buffer\n");
|
|
if (infile >= 0) close(infile);
|
|
return -1;
|
|
}
|
|
|
|
/* Check if we have enough space */
|
|
/* TODO: Check the size of the partition. */
|
|
if (ipod->nimages > 1) {
|
|
bytesavailable=ipod->ipod_directory[1].devOffset-ipod->ipod_directory[0].devOffset;
|
|
if (bytesavailable < newsize) {
|
|
fprintf(stderr,"[INFO] Moving images to create room for new firmware...\n");
|
|
|
|
/* TODO: Implement image movement */
|
|
fprintf(stderr,"[ERR] Image movement not yet implemented.\n");
|
|
close(infile);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_BOOTOBJS
|
|
if (type == FILETYPE_INTERNAL) {
|
|
memcpy(ipod_sectorbuf,ipod->bootloader,ipod->bootloader_len);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
fprintf(stderr,"[INFO] Reading input file...\n");
|
|
/* We now know we have enough space, so write it. */
|
|
n = read(infile,ipod_sectorbuf,length);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Couldn't read input file\n");
|
|
close(infile);
|
|
return -1;
|
|
}
|
|
close(infile);
|
|
}
|
|
|
|
/* Pad the data with zeros */
|
|
memset(ipod_sectorbuf+length,0,newsize-length);
|
|
|
|
if (type==FILETYPE_DOT_IPOD) {
|
|
chksum = ipod->modelnum;
|
|
for (i = 0; i < length; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
chksum += ipod_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;
|
|
}
|
|
}
|
|
|
|
|
|
offset = ipod->fwoffset+ipod->ipod_directory[ipod->ososimage].devOffset;
|
|
|
|
if (ipod->modelnum==62) {
|
|
|
|
/* 2nd Gen Nano has encrypted firmware, and the sector
|
|
preceeding the firmware contains hashes that need to be
|
|
preserved. Nano 2G images include these extra 2048 (0x800)
|
|
bytes
|
|
*/
|
|
|
|
offset -= 0x800;
|
|
|
|
/* TODO: The above checks need to take into account this 0x800 bytes */
|
|
}
|
|
|
|
if (ipod_seek(ipod, offset) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_write(ipod,ipod_sectorbuf,newsize)) < 0) {
|
|
perror("[ERR] Write failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (n < newsize) {
|
|
fprintf(stderr,"[ERR] Short write - requested %d bytes, received %d\n"
|
|
,newsize,n);
|
|
return -1;
|
|
}
|
|
fprintf(stderr,"[INFO] Wrote %d bytes to firmware partition\n",n);
|
|
|
|
/* Now we need to update the "len", "entryOffset" and "chksum" fields
|
|
|
|
NOTE: On the Nano 2G, the checksum is the checksum of the
|
|
unencrypted firmware. But this isn't checked by the NOR
|
|
bootloader (there are cryptographic hashes in the
|
|
firmware itself), so it doesn't matter that this is
|
|
wrong.
|
|
*/
|
|
chksum = 0;
|
|
for (i = 0; i < length; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
chksum += ipod_sectorbuf[i];
|
|
}
|
|
|
|
x = ipod->diroffset % ipod->sector_size;
|
|
|
|
/* Read directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) { return -1; }
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) { return -1; }
|
|
|
|
/* Update entries for image */
|
|
p = ipod_sectorbuf + x + (ipod->ososimage * 40);
|
|
int2le(length - (ipod->modelnum==62 ? 0x800: 0), p + 16);
|
|
int2le(0, p + 24);
|
|
int2le(chksum, p + 28);
|
|
|
|
/* Write directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) { return -1; }
|
|
n=ipod_write(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) { return -1; }
|
|
|
|
return 0;
|
|
}
|
|
|
|
int read_firmware(struct ipod_t* ipod, char* filename, int type)
|
|
{
|
|
int length;
|
|
int i;
|
|
int outfile;
|
|
int n;
|
|
unsigned long offset;
|
|
unsigned long chksum=0; /* 32 bit checksum - Rockbox .ipod style*/
|
|
unsigned char header[8]; /* Header for .ipod file */
|
|
|
|
if (ipod->ipod_directory[ipod->ososimage].entryOffset != 0) {
|
|
/* We have a bootloader... */
|
|
length = ipod->ipod_directory[ipod->ososimage].entryOffset;
|
|
} else {
|
|
length = ipod->ipod_directory[ipod->ososimage].len;
|
|
}
|
|
|
|
fprintf(stderr,"[INFO] Reading firmware (%d bytes)\n",length);
|
|
|
|
offset = ipod->fwoffset + ipod->ipod_directory[ipod->ososimage].devOffset;
|
|
i = (length+ipod->sector_size-1) & ~(ipod->sector_size-1);
|
|
fprintf(stderr,"[INFO] Padding read from 0x%08x to 0x%08x bytes\n",
|
|
length,i);
|
|
|
|
if (ipod->modelnum==62) {
|
|
/* 2nd Gen Nano has encrypted firmware, and we need to dump the
|
|
sector preceeding the image - it contains hashes */
|
|
offset -= 0x800;
|
|
length += 0x800;
|
|
i += 0x800;
|
|
}
|
|
|
|
if (ipod_seek(ipod, offset)) {
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_read(ipod,ipod_sectorbuf,i)) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (n < i) {
|
|
fprintf(stderr,"[ERR] Short read - requested %d bytes, received %d\n",
|
|
i,n);
|
|
return -1;
|
|
}
|
|
|
|
outfile = open(filename,O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666);
|
|
if (outfile < 0) {
|
|
fprintf(stderr,"[ERR] Couldn't open file %s\n",filename);
|
|
return -1;
|
|
}
|
|
|
|
if (type == FILETYPE_DOT_IPOD) {
|
|
chksum = ipod->modelnum;
|
|
for (i = 0; i < length; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
chksum += ipod_sectorbuf[i];
|
|
}
|
|
|
|
int2be(chksum,header);
|
|
memcpy(header+4, ipod->modelname,4);
|
|
|
|
n = write(outfile,header,8);
|
|
if (n != 8) {
|
|
fprintf(stderr,"[ERR] Write error - %d\n",n);
|
|
}
|
|
}
|
|
|
|
n = write(outfile,ipod_sectorbuf,length);
|
|
if (n != length) {
|
|
fprintf(stderr,"[ERR] Write error - %d\n",n);
|
|
}
|
|
close(outfile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int read_directory(struct ipod_t* ipod)
|
|
{
|
|
int n;
|
|
int x;
|
|
unsigned char* p;
|
|
unsigned short version;
|
|
|
|
ipod->nimages=0;
|
|
|
|
/* Read firmware partition header (first 512 bytes of disk - but
|
|
let's read a whole sector) */
|
|
|
|
if (ipod_seek(ipod, ipod->start) < 0) {
|
|
fprintf(stderr,"[ERR] Seek to 0x%08x in read_directory() failed.\n",
|
|
(unsigned int)(ipod->start));
|
|
return -1;
|
|
}
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] ipod_read(ipod,buf,0x%08x) failed in read_directory()\n", ipod->sector_size);
|
|
return -1;
|
|
}
|
|
|
|
if (memcmp(ipod_sectorbuf,apple_stop_sign,sizeof(apple_stop_sign))!=0) {
|
|
fprintf(stderr,"[ERR] Firmware partition doesn't contain Apple copyright, aborting.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (memcmp(ipod_sectorbuf+0x100,"]ih[",4)!=0) {
|
|
fprintf(stderr,"[ERR] Bad firmware directory\n");
|
|
return -1;
|
|
}
|
|
|
|
version = le2ushort(ipod_sectorbuf+0x10a);
|
|
if ((version != 2) && (version != 3)) {
|
|
fprintf(stderr,"[ERR] Unknown firmware format version %04x\n",
|
|
version);
|
|
}
|
|
ipod->diroffset=le2int(ipod_sectorbuf+0x104) + 0x200;
|
|
|
|
/* diroffset may not be sector-aligned */
|
|
x = ipod->diroffset % ipod->sector_size;
|
|
|
|
/* Read directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) {
|
|
fprintf(stderr,"[ERR] Seek to diroffset (%08x) failed.\n",(unsigned)ipod->diroffset);
|
|
return -1;
|
|
}
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Read of directory failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
p = ipod_sectorbuf + x;
|
|
|
|
/* A hack to detect 2nd gen Nanos - maybe there is a better way? */
|
|
if (p[0] == 0)
|
|
{
|
|
/* Adjust diroffset */
|
|
ipod->diroffset += ipod->sector_size - x;
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Read of directory failed.\n");
|
|
return -1;
|
|
}
|
|
p = ipod_sectorbuf;
|
|
}
|
|
|
|
ipod->ososimage = -1;
|
|
while ((ipod->nimages < MAX_IMAGES) && (p < (ipod_sectorbuf + x + 400)) &&
|
|
((memcmp(p,"!ATA",4)==0) || (memcmp(p,"DNAN",4)==0))) {
|
|
p+=4;
|
|
if (memcmp(p,"soso",4)==0) {
|
|
ipod->ipod_directory[ipod->nimages].ftype=FTYPE_OSOS;
|
|
ipod->ososimage = ipod->nimages;
|
|
} else if (memcmp(p,"crsr",4)==0) {
|
|
ipod->ipod_directory[ipod->nimages].ftype=FTYPE_RSRC;
|
|
} else if (memcmp(p,"dpua",4)==0) {
|
|
ipod->ipod_directory[ipod->nimages].ftype=FTYPE_AUPD;
|
|
} else if (memcmp(p,"kbso",4)==0) {
|
|
ipod->ipod_directory[ipod->nimages].ftype=FTYPE_OSBK;
|
|
} else if (memcmp(p,"ebih",4)==0) {
|
|
ipod->ipod_directory[ipod->nimages].ftype=FTYPE_HIBE;
|
|
} else {
|
|
fprintf(stderr,"[ERR] Unknown image type %c%c%c%c\n",
|
|
p[0],p[1],p[2],p[3]);
|
|
}
|
|
p+=4;
|
|
ipod->ipod_directory[ipod->nimages].id=le2int(p);
|
|
p+=4;
|
|
ipod->ipod_directory[ipod->nimages].devOffset=le2int(p);
|
|
p+=4;
|
|
ipod->ipod_directory[ipod->nimages].len=le2int(p);
|
|
p+=4;
|
|
ipod->ipod_directory[ipod->nimages].addr=le2int(p);
|
|
p+=4;
|
|
ipod->ipod_directory[ipod->nimages].entryOffset=le2int(p);
|
|
p+=4;
|
|
ipod->ipod_directory[ipod->nimages].chksum=le2int(p);
|
|
p+=4;
|
|
ipod->ipod_directory[ipod->nimages].vers=le2int(p);
|
|
p+=4;
|
|
ipod->ipod_directory[ipod->nimages].loadAddr=le2int(p);
|
|
p+=4;
|
|
ipod->nimages++;
|
|
}
|
|
|
|
if (ipod->ososimage < 0) {
|
|
fprintf(stderr,"[ERR] No OSOS image found.\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((ipod->nimages > 1) && (version==2)) {
|
|
/* The 3g firmware image doesn't appear to have a version, so
|
|
let's make one up... Note that this is never written back to the
|
|
ipod, so it's OK to do. */
|
|
|
|
if (ipod->ipod_directory[ipod->ososimage].vers == 0) { ipod->ipod_directory[ipod->ososimage].vers = 3; }
|
|
|
|
ipod->fwoffset = ipod->start;
|
|
} else {
|
|
ipod->fwoffset = ipod->start + ipod->sector_size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int list_images(struct ipod_t* ipod)
|
|
{
|
|
int i;
|
|
|
|
if (ipod_verbose) {
|
|
printf(" Type id devOffset len addr entryOffset chksum vers loadAddr devOffset+len\n");
|
|
for (i = 0 ; i < ipod->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->ipod_directory[i].ftype],
|
|
ipod->ipod_directory[i].id,
|
|
ipod->ipod_directory[i].devOffset,
|
|
ipod->ipod_directory[i].len,
|
|
ipod->ipod_directory[i].addr,
|
|
ipod->ipod_directory[i].entryOffset,
|
|
ipod->ipod_directory[i].chksum,
|
|
ipod->ipod_directory[i].vers,
|
|
ipod->ipod_directory[i].loadAddr,
|
|
ipod->ipod_directory[i].devOffset+((ipod->ipod_directory[i].len+ipod->sector_size-1)&~(ipod->sector_size-1)));
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
printf("Listing firmware partition contents:\n");
|
|
printf("\n");
|
|
|
|
for (i = 0 ; i < ipod->nimages; i++) {
|
|
printf("Image %d:\n",i+1);
|
|
switch(ipod->ipod_directory[i].ftype) {
|
|
case FTYPE_OSOS:
|
|
if (ipod->ipod_directory[i].entryOffset==0) {
|
|
printf(" Main firmware - %d bytes\n",
|
|
ipod->ipod_directory[i].len);
|
|
} else {
|
|
printf(" Main firmware - %d bytes\n",
|
|
ipod->ipod_directory[i].entryOffset);
|
|
printf(" Third-party bootloader - %d bytes\n",
|
|
ipod->ipod_directory[i].len-ipod->ipod_directory[i].entryOffset);
|
|
}
|
|
break;
|
|
default:
|
|
printf(" %s - %d bytes\n",
|
|
ftypename[ipod->ipod_directory[i].ftype],
|
|
ipod->ipod_directory[i].len);
|
|
}
|
|
}
|
|
printf("\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int getmodel(struct ipod_t* ipod, int ipod_version)
|
|
{
|
|
switch (ipod_version) {
|
|
case 0x01:
|
|
ipod->modelstr="1st or 2nd Generation";
|
|
ipod->modelnum = 19;
|
|
ipod->modelname = "1g2g";
|
|
ipod->targetname = "ipod1g2g";
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipod1g2g;
|
|
ipod->bootloader_len = LEN_ipod1g2g;
|
|
#endif
|
|
break;
|
|
case 0x02:
|
|
ipod->modelstr="3rd Generation";
|
|
ipod->modelnum = 7;
|
|
ipod->modelname = "ip3g";
|
|
ipod->targetname = "ipod3g";
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipod3g;
|
|
ipod->bootloader_len = LEN_ipod3g;
|
|
#endif
|
|
break;
|
|
case 0x40:
|
|
ipod->modelstr="1st Generation Mini";
|
|
ipod->modelnum = 9;
|
|
ipod->modelname = "mini";
|
|
ipod->targetname = "ipodmini1g";
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipodmini;
|
|
ipod->bootloader_len = LEN_ipodmini;
|
|
#endif
|
|
break;
|
|
case 0x50:
|
|
ipod->modelstr="4th Generation";
|
|
ipod->modelnum = 8;
|
|
ipod->modelname = "ip4g";
|
|
ipod->targetname = "ipod4gray";
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipod4g;
|
|
ipod->bootloader_len = LEN_ipod4g;
|
|
#endif
|
|
break;
|
|
case 0x60:
|
|
ipod->modelstr="Photo/Color";
|
|
ipod->modelnum = 3;
|
|
ipod->modelname = "ipco";
|
|
ipod->targetname = "ipodcolor";
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipodcolor;
|
|
ipod->bootloader_len = LEN_ipodcolor;
|
|
#endif
|
|
break;
|
|
case 0x70:
|
|
ipod->modelstr="2nd Generation Mini";
|
|
ipod->modelnum = 11;
|
|
ipod->modelname = "mn2g";
|
|
ipod->targetname = "ipodmini2g";
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipodmini2g;
|
|
ipod->bootloader_len = LEN_ipodmini2g;
|
|
#endif
|
|
break;
|
|
case 0xc0:
|
|
ipod->modelstr="1st Generation Nano";
|
|
ipod->modelnum = 4;
|
|
ipod->modelname = "nano";
|
|
ipod->targetname = "ipodnano";
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipodnano;
|
|
ipod->bootloader_len = LEN_ipodnano;
|
|
#endif
|
|
break;
|
|
case 0xb0:
|
|
ipod->modelstr="Video (aka 5th Generation)";
|
|
ipod->modelnum = 5;
|
|
ipod->modelname = "ipvd";
|
|
if(ipod->ramsize == 64) {
|
|
ipod->targetname = "ipodvideo64mb";
|
|
}
|
|
else {
|
|
ipod->targetname = "ipodvideo";
|
|
}
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipodvideo;
|
|
ipod->bootloader_len = LEN_ipodvideo;
|
|
#endif
|
|
break;
|
|
case 0x100:
|
|
ipod->modelstr="2nd Generation Nano";
|
|
ipod->modelnum = 62;
|
|
ipod->modelname = "nn2x";
|
|
ipod->targetname = "ipodnano2g";
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = ipodnano2g;
|
|
ipod->bootloader_len = LEN_ipodnano2g;
|
|
#endif
|
|
break;
|
|
default:
|
|
ipod->modelname = NULL;
|
|
ipod->modelnum = 0;
|
|
ipod->targetname = NULL;
|
|
#ifdef WITH_BOOTOBJS
|
|
ipod->bootloader = NULL;
|
|
ipod->bootloader_len = 0;
|
|
#endif
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* returns number of found ipods or -1 if no ipods found and permission
|
|
* for raw disc access was denied. */
|
|
int ipod_scan(struct ipod_t* ipod)
|
|
{
|
|
int i;
|
|
int n = 0;
|
|
int ipod_version;
|
|
char last_ipod[4096];
|
|
int denied = 0;
|
|
int result;
|
|
|
|
printf("[INFO] Scanning disk devices...\n");
|
|
|
|
for (i = 0; i <= 25 ; i++) {
|
|
#ifdef __WIN32__
|
|
sprintf(ipod->diskname,"\\\\.\\PhysicalDrive%d",i);
|
|
#elif defined(linux) || defined (__linux)
|
|
sprintf(ipod->diskname,"/dev/sd%c",'a'+i);
|
|
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \
|
|
|| defined(__bsdi__) || defined(__DragonFly__)
|
|
sprintf(ipod->diskname,"/dev/da%d",i);
|
|
#elif defined(__APPLE__) && defined(__MACH__)
|
|
sprintf(ipod->diskname,"/dev/disk%d",i);
|
|
#else
|
|
#error No disk paths defined for this platform
|
|
#endif
|
|
if ((result = ipod_open(ipod, 1)) < 0) {
|
|
if(result == -2) {
|
|
denied++;
|
|
}
|
|
ipod_close(ipod);
|
|
continue;
|
|
}
|
|
|
|
if (read_partinfo(ipod,1) < 0) {
|
|
ipod_close(ipod);
|
|
continue;
|
|
}
|
|
|
|
if ((ipod->pinfo[0].start==0) || (ipod->pinfo[0].type != 0)) {
|
|
ipod_close(ipod);
|
|
continue;
|
|
}
|
|
|
|
if (read_directory(ipod) < 0) {
|
|
ipod_close(ipod);
|
|
continue;
|
|
}
|
|
|
|
ipod_version=(ipod->ipod_directory[ipod->ososimage].vers>>8);
|
|
/* Windows requires the ipod in R/W mode for SCSI Inquiry */
|
|
ipod->ramsize = 0;
|
|
ipod_reopen_rw(ipod);
|
|
ipod_get_xmlinfo(ipod);
|
|
ipod_get_ramsize(ipod);
|
|
if (getmodel(ipod,ipod_version) < 0) {
|
|
ipod_close(ipod);
|
|
continue;
|
|
}
|
|
|
|
#ifdef __WIN32__
|
|
printf("[INFO] Ipod found - %s (\"%s\") - disk device %d\n",
|
|
ipod->modelstr,ipod->macpod ? "macpod" : "winpod",i);
|
|
#else
|
|
printf("[INFO] Ipod found - %s (\"%s\") - %s\n",
|
|
ipod->modelstr,ipod->macpod ? "macpod" : "winpod",ipod->diskname);
|
|
#endif
|
|
n++;
|
|
strcpy(last_ipod,ipod->diskname);
|
|
ipod_close(ipod);
|
|
}
|
|
|
|
if (n==1) {
|
|
/* Remember the disk name */
|
|
strcpy(ipod->diskname,last_ipod);
|
|
}
|
|
else if(n == 0 && denied) {
|
|
printf("[ERR] FATAL: Permission denied on %d device(s) and no ipod detected.\n", denied);
|
|
#ifdef __WIN32__
|
|
printf("[ERR] You need to run this program with administrator priviledges!\n");
|
|
#else
|
|
printf("[ERR] You need permissions for raw disc access for this program to work!\n");
|
|
#endif
|
|
}
|
|
return (n == 0 && denied) ? -1 : n;
|
|
}
|
|
|
|
static void put_int32le(uint32_t x, unsigned char* p)
|
|
{
|
|
p[0] = x & 0xff;
|
|
p[1] = (x >> 8) & 0xff;
|
|
p[2] = (x >> 16) & 0xff;
|
|
p[3] = (x >> 24) & 0xff;
|
|
}
|
|
|
|
int write_dos_partition_table(struct ipod_t* ipod)
|
|
{
|
|
unsigned char* p;
|
|
int i, n;
|
|
uint32_t type;
|
|
|
|
/* Only support 512-byte sectors at the moment */
|
|
if ( ipod->sector_size != 512 )
|
|
{
|
|
fprintf(stderr,"[ERR] Only ipods with 512 bytes per sector are supported.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Firstly zero the entire MBR */
|
|
memset(ipod_sectorbuf, 0, ipod->sector_size);
|
|
|
|
/* Now add the partition info */
|
|
for (i=0; i < 4 ; i++)
|
|
{
|
|
p = ipod_sectorbuf + 0x1be + i*16;
|
|
|
|
/* Ensure first partition is type 0, and second is 0xb */
|
|
if (i==0) { type = 0; }
|
|
else if (i==1) { type = 0xb; }
|
|
else { type = ipod->pinfo[i].type; }
|
|
|
|
put_int32le(type, p + 4);
|
|
put_int32le(ipod->pinfo[i].start, p + 8);
|
|
put_int32le(ipod->pinfo[i].size, p + 12);
|
|
}
|
|
|
|
/* Finally add the magic */
|
|
ipod_sectorbuf[0x1fe] = 0x55;
|
|
ipod_sectorbuf[0x1ff] = 0xaa;
|
|
|
|
if (ipod_seek(ipod, 0) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed writing MBR\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Write MBR */
|
|
if ((n = ipod_write(ipod, ipod_sectorbuf, ipod->sector_size)) < 0) {
|
|
perror("[ERR] Write failed\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Get the XML Device Information, as documented here:
|
|
|
|
http://www.ipodlinux.org/wiki/Device_Information
|
|
*/
|
|
|
|
int ipod_get_xmlinfo(struct ipod_t* ipod)
|
|
{
|
|
unsigned char hdr[255];
|
|
unsigned char buf[255];
|
|
char* p;
|
|
int psize;
|
|
int npages;
|
|
int i;
|
|
|
|
if (ipod_scsi_inquiry(ipod, 0xc0, buf, sizeof(buf)) < 0)
|
|
{
|
|
fprintf(stderr,"[ERR] Sending SCSI Command failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Reading directly into hdr[] causes problems (for an unknown reason) on
|
|
win32 */
|
|
memcpy(hdr, buf, sizeof(hdr));
|
|
|
|
npages = hdr[3];
|
|
|
|
psize = npages * 0xf8; /* Hopefully this is enough. */
|
|
|
|
ipod->xmlinfo = malloc(psize);
|
|
ipod->xmlinfo_len = 0;
|
|
|
|
if (ipod->xmlinfo == NULL) {
|
|
fprintf(stderr,"[ERR] Could not allocate RAM for xmlinfo\n");
|
|
return -1;
|
|
}
|
|
|
|
p = ipod->xmlinfo;
|
|
|
|
for (i=0; i < npages; i++) {
|
|
if (ipod_scsi_inquiry(ipod, hdr[i+4], buf, sizeof(buf)) < 0) {
|
|
fprintf(stderr,"[ERR] Sending SCSI Command failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((buf[3] + ipod->xmlinfo_len) > psize) {
|
|
fprintf(stderr,"[ERR] Ran out of memory reading xmlinfo\n");
|
|
free(ipod->xmlinfo);
|
|
ipod->xmlinfo = NULL;
|
|
ipod->xmlinfo_len = 0;
|
|
return -1;
|
|
}
|
|
|
|
memcpy(p, buf + 4, buf[3]);
|
|
p += buf[3];
|
|
ipod->xmlinfo_len += buf[3];
|
|
}
|
|
|
|
/* NULL-terminate the XML info */
|
|
*p = 0;
|
|
|
|
fprintf(stderr,"[INFO] Read XML info (%d bytes)\n",ipod->xmlinfo_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ipod_get_ramsize(struct ipod_t* ipod)
|
|
{
|
|
const char needle[] = "<key>RAM</key>\n<integer>";
|
|
char* p;
|
|
|
|
if (ipod->xmlinfo == NULL)
|
|
return;
|
|
|
|
p = strstr(ipod->xmlinfo, needle);
|
|
|
|
if (p) {
|
|
ipod->ramsize = atoi(p + sizeof(needle) - 1);
|
|
}
|
|
}
|
|
|
|
#ifndef RBUTIL
|
|
|
|
static inline uint32_t getuint32le(unsigned char* buf)
|
|
{
|
|
int32_t res = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
|
|
|
|
return res;
|
|
}
|
|
|
|
/* testMarker and GetSecurityBlockKey based on code from BadBlocks and
|
|
Kingstone, posted at http://ipodlinux.org/Flash_Decryption
|
|
|
|
*/
|
|
|
|
static bool testMarker(int marker)
|
|
{
|
|
int mask, decrypt, temp1, temp2;
|
|
|
|
mask = (marker&0xff)|((marker&0xff)<<8)|((marker&0xff)<<16)|((marker&0xff)<<24);
|
|
decrypt = marker ^ mask;
|
|
temp1=(int)((unsigned int)decrypt>>24);
|
|
temp2=decrypt<<8;
|
|
|
|
if (temp1==0)
|
|
return false;
|
|
|
|
temp2=(int)((unsigned int)temp2>>24);
|
|
decrypt=decrypt<<16;
|
|
decrypt=(int)((unsigned int)decrypt>>24);
|
|
|
|
if ((temp1 < temp2) && (temp2 < decrypt))
|
|
{
|
|
temp1 = temp1 & 0xf;
|
|
temp2 = temp2 & 0xf;
|
|
decrypt = decrypt & 0xf;
|
|
|
|
if ((temp1 > temp2) && (temp2 > decrypt) && (decrypt != 0))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int GetSecurityBlockKey(unsigned char *data, unsigned char* this_key)
|
|
{
|
|
int constant = 0x54c3a298;
|
|
int key=0;
|
|
int nkeys = 0;
|
|
int aMarker=0;
|
|
int pos=0;
|
|
int c, count;
|
|
int temp1;
|
|
static const int offset[8]={0x5,0x25,0x6f,0x69,0x15,0x4d,0x40,0x34};
|
|
|
|
for (c = 0; c < 8; c++)
|
|
{
|
|
pos = offset[c]*4;
|
|
aMarker = getuint32le(data + pos);
|
|
|
|
if (testMarker(aMarker))
|
|
{
|
|
if (c<7)
|
|
pos =(offset[c+1]*4)+4;
|
|
else
|
|
pos =(offset[0]*4)+4;
|
|
|
|
key=0;
|
|
|
|
temp1=aMarker;
|
|
|
|
for (count=0;count<2;count++){
|
|
int word = getuint32le(data + pos);
|
|
temp1 = aMarker;
|
|
temp1 = temp1^word;
|
|
temp1 = temp1^constant;
|
|
key = temp1;
|
|
pos = pos+4;
|
|
}
|
|
int r1=0x6f;
|
|
int r2=0;
|
|
int r12;
|
|
int r14;
|
|
unsigned int r_tmp;
|
|
|
|
for (count=2;count<128;count=count+2){
|
|
r2=getuint32le(data+count*4);
|
|
r12=getuint32le(data+(count*4)+4);
|
|
r_tmp=(unsigned int)r12>>16;
|
|
r14=r2 | ((int)r_tmp);
|
|
r2=r2&0xffff;
|
|
r2=r2 | r12;
|
|
r1=r1^r14;
|
|
r1=r1+r2;
|
|
}
|
|
key=key^r1;
|
|
|
|
// Invert key, little endian
|
|
this_key[0] = key & 0xff;
|
|
this_key[1] = (key >> 8) & 0xff;
|
|
this_key[2] = (key >> 16) & 0xff;
|
|
this_key[3] = (key >> 24) & 0xff;
|
|
nkeys++;
|
|
}
|
|
}
|
|
return nkeys;
|
|
}
|
|
|
|
static int find_key(struct ipod_t* ipod, int aupd, unsigned char* key)
|
|
{
|
|
int n;
|
|
|
|
/* Firstly read the security block and find the RC4 key. This is
|
|
in the sector preceeding the AUPD image. */
|
|
|
|
fprintf(stderr, "[INFO] Reading security block at offset 0x%08x\n",ipod->ipod_directory[aupd].devOffset-ipod->sector_size);
|
|
if (ipod_seek(ipod, ipod->fwoffset+ipod->ipod_directory[aupd].devOffset-ipod->sector_size) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_read(ipod, ipod_sectorbuf, 512)) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
n = GetSecurityBlockKey(ipod_sectorbuf, key);
|
|
|
|
if (n != 1)
|
|
{
|
|
fprintf(stderr, "[ERR] %d keys found in security block, can not continue\n",n);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int read_aupd(struct ipod_t* ipod, char* filename)
|
|
{
|
|
int length;
|
|
int i;
|
|
int outfile;
|
|
int n;
|
|
int aupd;
|
|
struct rc4_key_t rc4;
|
|
unsigned char key[4];
|
|
unsigned long chksum=0;
|
|
|
|
aupd = 0;
|
|
while ((aupd < ipod->nimages) && (ipod->ipod_directory[aupd].ftype != FTYPE_AUPD))
|
|
{
|
|
aupd++;
|
|
}
|
|
|
|
if (aupd == ipod->nimages)
|
|
{
|
|
fprintf(stderr,"[ERR] No AUPD image in firmware partition.\n");
|
|
return -1;
|
|
}
|
|
|
|
length = ipod->ipod_directory[aupd].len;
|
|
|
|
fprintf(stderr,"[INFO] Reading firmware (%d bytes)\n",length);
|
|
|
|
if (find_key(ipod, aupd, key) < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr, "[INFO] Decrypting AUPD image with key %02x%02x%02x%02x\n",key[0],key[1],key[2],key[3]);
|
|
|
|
if (ipod_seek(ipod, ipod->fwoffset+ipod->ipod_directory[aupd].devOffset) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
i = (length+ipod->sector_size-1) & ~(ipod->sector_size-1);
|
|
|
|
if ((n = ipod_read(ipod,ipod_sectorbuf,i)) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (n < i) {
|
|
fprintf(stderr,"[ERR] Short read - requested %d bytes, received %d\n",
|
|
i,n);
|
|
return -1;
|
|
}
|
|
|
|
/* Perform the decryption - this is standard (A)RC4 */
|
|
matrixArc4Init(&rc4, key, 4);
|
|
matrixArc4(&rc4, ipod_sectorbuf, ipod_sectorbuf, length);
|
|
|
|
chksum = 0;
|
|
for (i = 0; i < (int)length; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
chksum += ipod_sectorbuf[i];
|
|
}
|
|
|
|
if (chksum != ipod->ipod_directory[aupd].chksum)
|
|
{
|
|
fprintf(stderr,"[ERR] Decryption failed - checksum error\n");
|
|
return -1;
|
|
}
|
|
fprintf(stderr,"[INFO] Decrypted OK (checksum matches header)\n");
|
|
|
|
outfile = open(filename,O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666);
|
|
if (outfile < 0) {
|
|
fprintf(stderr,"[ERR] Couldn't open file %s\n",filename);
|
|
return -1;
|
|
}
|
|
|
|
n = write(outfile,ipod_sectorbuf,length);
|
|
if (n != length) {
|
|
fprintf(stderr,"[ERR] Write error - %d\n",n);
|
|
}
|
|
close(outfile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int write_aupd(struct ipod_t* ipod, char* filename)
|
|
{
|
|
unsigned int length;
|
|
int i;
|
|
int x;
|
|
int n;
|
|
int infile;
|
|
int newsize;
|
|
int aupd;
|
|
unsigned long chksum=0;
|
|
struct rc4_key_t rc4;
|
|
unsigned char key[4];
|
|
|
|
/* 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;
|
|
}
|
|
|
|
length = filesize(infile);
|
|
newsize=(length+ipod->sector_size-1)&~(ipod->sector_size-1);
|
|
|
|
fprintf(stderr,"[INFO] Padding input file from 0x%08x to 0x%08x bytes\n",
|
|
length,newsize);
|
|
|
|
if (newsize > BUFFER_SIZE) {
|
|
fprintf(stderr,"[ERR] Input file too big for buffer\n");
|
|
if (infile >= 0) close(infile);
|
|
return -1;
|
|
}
|
|
|
|
/* Find aupd image number */
|
|
aupd = 0;
|
|
while ((aupd < ipod->nimages) && (ipod->ipod_directory[aupd].ftype != FTYPE_AUPD))
|
|
{
|
|
aupd++;
|
|
}
|
|
|
|
if (aupd == ipod->nimages)
|
|
{
|
|
fprintf(stderr,"[ERR] No AUPD image in firmware partition.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (length != ipod->ipod_directory[aupd].len)
|
|
{
|
|
fprintf(stderr,"[ERR] AUPD image (%d bytes) differs in size to %s (%d bytes).\n",
|
|
ipod->ipod_directory[aupd].len, filename, length);
|
|
return -1;
|
|
}
|
|
|
|
if (find_key(ipod, aupd, key) < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr, "[INFO] Encrypting AUPD image with key %02x%02x%02x%02x\n",key[0],key[1],key[2],key[3]);
|
|
|
|
/* We now know we have enough space, so write it. */
|
|
|
|
fprintf(stderr,"[INFO] Reading input file...\n");
|
|
n = read(infile,ipod_sectorbuf,length);
|
|
if (n < 0) {
|
|
fprintf(stderr,"[ERR] Couldn't read input file\n");
|
|
close(infile);
|
|
return -1;
|
|
}
|
|
close(infile);
|
|
|
|
/* Pad the data with zeros */
|
|
memset(ipod_sectorbuf+length,0,newsize-length);
|
|
|
|
/* Calculate the new checksum (before we encrypt) */
|
|
chksum = 0;
|
|
for (i = 0; i < (int)length; i++) {
|
|
/* add 8 unsigned bits but keep a 32 bit sum */
|
|
chksum += ipod_sectorbuf[i];
|
|
}
|
|
|
|
/* Perform the encryption - this is standard (A)RC4 */
|
|
matrixArc4Init(&rc4, key, 4);
|
|
matrixArc4(&rc4, ipod_sectorbuf, ipod_sectorbuf, length);
|
|
|
|
if (ipod_seek(ipod, ipod->fwoffset+ipod->ipod_directory[aupd].devOffset) < 0) {
|
|
fprintf(stderr,"[ERR] Seek failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((n = ipod_write(ipod,ipod_sectorbuf,newsize)) < 0) {
|
|
perror("[ERR] Write failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (n < newsize) {
|
|
fprintf(stderr,"[ERR] Short write - requested %d bytes, received %d\n"
|
|
,newsize,n);
|
|
return -1;
|
|
}
|
|
fprintf(stderr,"[INFO] Wrote %d bytes to firmware partition\n",n);
|
|
|
|
x = ipod->diroffset % ipod->sector_size;
|
|
|
|
/* Read directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) { return -1; }
|
|
|
|
n=ipod_read(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) { return -1; }
|
|
|
|
/* Update checksum */
|
|
fprintf(stderr,"[INFO] Updating checksum to 0x%08x (was 0x%08x)\n",(unsigned int)chksum,le2int(ipod_sectorbuf + x + aupd*40 + 28));
|
|
int2le(chksum,ipod_sectorbuf+x+aupd*40+28);
|
|
|
|
/* Write directory */
|
|
if (ipod_seek(ipod, ipod->start + ipod->diroffset - x) < 0) { return -1; }
|
|
n=ipod_write(ipod, ipod_sectorbuf, ipod->sector_size);
|
|
if (n < 0) { return -1; }
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|