Subversion Repositories freemyipod

Rev

Rev 141 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
141 cmwslw 1
/*
2
 This program is free software; you can redistribute it and/or modify
3
 it under the terms of the GNU General Public License version 2.1 as
4
 published by the Free Software Foundation.
5
 
6
 This program is distributed in the hope that it will be useful,
7
 but WITHOUT ANY WARRANTY; without even the implied warranty of
8
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9
 GNU General Public License for more details.
10
 
11
 You should have received a copy of the GNU General Public License
12
 along with this program; if not, write to the Free Software
13
 Foundation Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
14
*/
15
 
16
/*
17
  $Id$
18
 
19
  Revision history:
20
  1.0 - 2007-01-25 - Brossillon J.Damien <brossill@enseirb.fr>
21
  -- Initial version
22
 
23
  1.1 - 2007-09-15
24
  -- Stuff added to guess the header versions.
25
 
142 cmwslw 26
  1.2 - 2010-08-15
27
  -- Added Nano 4G firmware support.
28
 
141 cmwslw 29
  Todo list:
30
  -- Debug
31
*/
32
 
33
#include <stdlib.h>
34
#include <stdio.h>
35
#include <string.h>
36
#include <getopt.h>
37
#include <limits.h>
38
 
39
#include "extract2g.h"
40
 
41
#define VERSION_H 1
142 cmwslw 42
#define VERSION_L 2
141 cmwslw 43
 
44
#define B (0x0FFFFFFF)
45
 
46
/* Change endian of a 4 bytes memory zone */
47
void change_endian( char* buf )
48
{
49
 
50
  char tmp[4];
51
 
52
  tmp[3] = buf[0];
53
  tmp[2] = buf[1];
54
  tmp[1] = buf[2];
55
  tmp[0] = buf[3];
56
 
57
  memcpy(buf,tmp,4*sizeof(char));
58
 
59
  return;
60
}
61
 
62
unsigned int hash_part(FILE* hIn, int start, int len, int header_len)
63
{
64
  unsigned int hash = 0;
65
 
66
  fseek(hIn, start, SEEK_SET);
67
 
68
  unsigned int tmp;
69
 
70
  int pos;
71
  for(pos=0;pos<(len+header_len);pos++)
72
  {
73
    tmp = fgetc(hIn);
74
 
75
    hash = (hash*B + tmp*pos);
76
  }
77
 
78
  return hash;
79
}
80
 
81
void extract_part(FILE* hIn, FILE* hOut,
82
                    char* name,
83
                    int start, int len,
84
                    int header_len)
85
{
86
  int pos;
87
  char tmp;
88
 
89
  char* filename = NULL;
90
 
91
  // No extract name specified
92
  if( !hOut )
93
  {
94
    filename = (char*)malloc( strlen(name) + 4 );
95
 
96
    if( !filename )
97
    {
98
      fprintf(stderr, "Memory allocation failure.");
99
      exit(EXIT_FAILURE);
100
    }
101
 
102
    strcpy(filename, name);
103
    strcat(filename, ".fw");
104
 
105
    hOut = fopen(filename, "wb");
106
  }
107
 
108
  fprintf(stdout,"Extracting from 0x%8.X to 0x%8.X in %s.\n",
109
           start, start+len+header_len, filename);
110
 
111
  // Go to the beginning of the part
112
  fseek(hIn, start, SEEK_SET);
113
 
114
  // Extracting
115
  for(pos=0;pos<(len+header_len);pos++)
116
  {
117
    tmp = fgetc(hIn);
118
    fputc(tmp,hOut);
119
  }
120
 
121
  fclose(hOut);
122
 
123
  free(filename);
124
 
125
  return;
126
}
127
 
128
char is_valid( DIRECTORY* p_dir )
129
{
130
  // Test if the directory contain a valid header for the part.
131
  return ( !strncmp( p_dir->dev, "NAND", 4) 
132
            || !strncmp( p_dir->dev, "ATA!", 4) );
133
}
134
 
135
char check_header(FILE* fh, unsigned int l)
136
{
137
  DIRECTORY dir;
138
 
139
  fseek(fh, l, SEEK_SET);
140
  fread( (void*)&dir, LEN_DIR, sizeof(char), fh);
141
 
142
  change_endian(dir.dev);
143
 
144
  return is_valid(&dir);
145
}
146
 
147
int guess_directory_address(FILE* fh)
148
{
149
  // Check for 1G header
150
  if(check_header(fh,ADDR_DIR_1G))
151
  {
152
    printf("> iPod nano 1g header detected.\n");
153
    return ADDR_DIR_1G;
154
  }
155
 
156
  // Check for 2G header
157
  if(check_header(fh,ADDR_DIR_2G))
158
  {
159
    printf("> iPod nano 2g header detected.\n");
160
    return ADDR_DIR_2G;
161
  }
162
 
163
  // Check for 3G header
164
  if(check_header(fh,ADDR_DIR_3G))
165
  {
166
    printf("> iPod nano 3g header detected.\n");
167
    return ADDR_DIR_3G;
168
  }
169
 
170
  // At least try to find an header
171
  fseek(fh, 0, SEEK_SET);
172
 
173
  DIRECTORY dir;
174
 
175
  while(!feof(fh))
176
  {
177
    fread( (void*)&dir, LEN_DIR, sizeof(char), fh);
178
    change_endian(dir.dev);
179
 
180
    if(is_valid(&dir))
181
    {
182
      int addr = ftell(fh) - LEN_DIR;
183
      printf("> Unknown header found at 0x%X\n",addr);
184
      return addr;
185
    }
186
 
187
    fseek(fh, -LEN_DIR + 1, SEEK_CUR);
188
  }
189
 
190
  return ADDR_DIR_DEFAULT;
191
}
192
 
193
void print_directory_infos(DIRECTORY* p_dir, char extended)
194
{
195
  if( extended )
196
    printf("dev: %.4s type: %.4s\n\
197
id: %X\n\
198
devOffset: %X\n\
199
len: %X\n\
200
addr: %X\n\
201
entryOffset: %X\n\
202
checksum: %X\n\
203
version: %X\n\
204
loadAddr: %X\n\n",
205
            p_dir->dev,
206
            p_dir->type,
207
            p_dir->id,
208
            p_dir->devOffset,
209
            p_dir->len,
210
            p_dir->addr,
211
            p_dir->entryOffset,
212
            p_dir->checksum,
213
            p_dir->version,
214
            p_dir->loadAddr );
215
 
216
   else
217
      printf("dev: %.4s type: %.4s devOffset: %X len: %X\n",
218
              p_dir->dev,
219
              p_dir->type,
220
              p_dir->devOffset,
221
              p_dir->len);
222
 
223
}
224
 
225
 
226
/* Display help */
227
void usage(int status)
228
{
229
  if (status != EXIT_SUCCESS)
230
    fputs("Try `extract_2g --help' for more information.\n", stderr);
231
  else
232
  {
233
    fputs("Usage: extract_2g [OPTION] [FILE]\n", stdout);
234
    fprintf(stdout,"Read & extract iPod Nano 2G data parts from a FILE dump.\n\
235
\n\
142 cmwslw 236
-l, --list               only list avaible parts according to the dump directory\n\n\
237
-H, --hash               do a hash on every part of the dump\n\n\
238
-A, --all                extract ALL founded parts from dump (default names used)\n\n\
239
-4, --nano4g-compat      forces iPod Nano 3G firmwares to be recognised as iPod Nano 4G's\n\n\
240
-e, --extract=NAME       select which part you want to extract (default none)\n\
241
-o, --output=FILE        put the extracted part into FILE (default NAME.fw)\n\n\
242
-d, --directory-address  specify the directory address (default 0x%X )\n\
243
-a, --header-length      specify the header length of each part (default 0x800)\n\n\
244
-h, --help               display this help and exit\n", ADDR_DIR_DEFAULT);
141 cmwslw 245
  }
246
 
247
  exit(status);
248
}
249
 
250
int main(int argc, char **argv)
251
{
252
  FILE *file = NULL;
253
  FILE *hExtract = NULL;
254
 
255
  char str_target[32];
256
 
257
  char extract_is_set = 0;
258
  char extract_all = 0;
142 cmwslw 259
  char nano4g_compat = 0;
141 cmwslw 260
  char list_directories = 0;
261
  char is_found = 0;
262
  char hash_parts = 0;
263
 
264
  /* REMOVE */
265
  char c;
266
 
267
  int tmp;
268
 
269
  int i;
270
 
271
  int directories_size = 0;
272
  DIRECTORY* directories = NULL;
273
 
274
  int addr_directory = -1;
275
 
276
  int length_header = LEN_HEADER;
277
 
278
  /* Getopt short options */
142 cmwslw 279
  static char const short_options[] = "e:o:d:a:AHl4hv";
141 cmwslw 280
 
281
  /* Getopt long options */
282
  static struct option const long_options[] = {
283
    {"extract", required_argument, NULL, 'e'},
284
    {"output", required_argument, NULL, 'o'},
285
    {"hash", no_argument, NULL, 'H'},
286
    {"list", no_argument, NULL, 'l'},
287
    {"all", no_argument, NULL, 'A'},
288
    {"directory-address", required_argument, NULL, 'd'},
289
    {"header-length", required_argument, NULL, 'a'},
142 cmwslw 290
    {"nano4g-compat", no_argument, NULL, '4'},
141 cmwslw 291
    {"help", no_argument, NULL, 'h'},
292
    {"version", no_argument, NULL, 'v'},
293
    {NULL, 0, NULL, 0}
294
  };
295
 
296
  /* Some informations */
297
  printf("%s compiled at %s %s.\n\n",argv[0],__TIME__,__DATE__);
298
 
299
  /* Parse command line options.  */
300
  while ((c = getopt_long(argc, argv,
301
                          short_options, long_options, NULL)) != -1) {
302
    switch (c) {
303
    case 'o':
304
        hExtract = fopen(optarg,"wb");
305
 
306
        if(!hExtract)
307
        {
308
          fprintf(stderr,"Cannot write/create %s.\n", optarg);
309
          usage(EXIT_FAILURE);
310
        }
311
 
312
      break;
313
 
314
    case 'a':
315
      if ( (tmp = strtol(optarg, NULL, 0)) > 0 )
316
        length_header = tmp;
317
      else {
318
        fprintf(stderr, "Invalid `header-length' value `%s'.\n", optarg);
319
        usage(EXIT_FAILURE);
320
      }
321
      break;
322
 
323
    case 'd':
324
      if ( (tmp = strtol(optarg, NULL, 0)) > 0 )
325
        addr_directory = tmp;
326
      else {
327
        fprintf(stderr, "Invalid `directory-address' value `%s'.\n", optarg);
328
        usage(EXIT_FAILURE);
329
      }
330
      break;
331
 
332
    case 'e':
333
        strncpy(str_target,optarg,32);
334
        extract_is_set = 1;
335
      break;
336
 
337
    case 'H':
338
        hash_parts = 1;
339
      break;
340
 
341
    case 'l':
342
        list_directories = 1;
343
      break;
344
 
345
    case 'A':
346
        extract_all = 1;
347
      break;
348
 
142 cmwslw 349
    case '4':
350
        nano4g_compat = 1;
351
      break;
352
 
141 cmwslw 353
    case 'h':
354
      usage(EXIT_SUCCESS);
355
      break;
356
 
357
    case 'v':
358
      fprintf(stdout,"extract_2g version %d.%d\n",VERSION_H,VERSION_L);
359
      fputs("Copyright (C) 2007 Brossillon J.Damien\n\
360
This is free software. You may redistribute copies of it under the terms\n\
361
of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n\
362
There is NO WARRANTY, to the extent permitted by law.\n", stdout);
363
      exit(EXIT_SUCCESS);
364
      break;
365
 
366
    default:
367
      usage(EXIT_FAILURE);
368
    }
369
  }
370
 
371
  if (!argv[optind]) {
372
    fprintf(stderr,"Missing dump filename.\n");
373
    usage(EXIT_FAILURE);
374
  } else {
375
    if (!(file = fopen(argv[optind], "rb")))
376
    {
377
      fprintf(stderr,"Cannot open file `%s'.\n", argv[optind]);
378
      usage(EXIT_FAILURE);
379
    }
380
  }
381
 
382
 
383
  if( addr_directory < 0 )
384
    addr_directory = guess_directory_address(file);
385
  else
386
    addr_directory = ADDR_DIR_DEFAULT;
387
 
142 cmwslw 388
  if (addr_directory == ADDR_DIR_3G && nano4g_compat)
389
    printf("> iPod nano 4g compat. mode enabled\n");
141 cmwslw 390
 
142 cmwslw 391
 
141 cmwslw 392
  fseek(file, addr_directory, SEEK_SET);
393
 
394
  DIRECTORY dir;
395
 
396
  // List directories
397
  while(1)
398
  {
399
    /* FIXME
400
    for(i=0;i<(int)LEN_DIR;i++)
401
    {
402
      ((char*)&dir)[i] = fgetc(file);
403
    }*/
404
    fread( (void*)&dir, LEN_DIR, sizeof(char), file);
405
 
406
    change_endian(dir.dev);
407
    change_endian(dir.type);
408
 
409
    if( is_valid(&dir) )
410
    {
411
      directories_size++;
412
      directories = (DIRECTORY*)realloc(directories,
413
                                        directories_size*sizeof(DIRECTORY));
414
 
415
      if(!directories)
416
      {
417
        fprintf(stderr,"Memory allocation failure.\n");
418
        exit(EXIT_FAILURE);
419
      }
142 cmwslw 420
 
421
	  if (addr_directory == ADDR_DIR_3G && nano4g_compat)
422
        dir.devOffset += 4096;
423
 
141 cmwslw 424
      memcpy(directories + directories_size - 1, &dir, sizeof(DIRECTORY));
425
    }
426
    else
427
      break;
428
  }
429
 
430
  // Check if we have, at least, one valid part
431
  if( directories_size <= 0 )
432
  {
433
    fclose(file);
434
 
435
    if(directories)
436
      free(directories);
437
 
438
    fprintf(stderr, "Cannot find at least one valid part in the dump.\n");
439
    exit(EXIT_FAILURE);
440
  }
441
 
442
  // User want to hash every parts of the dump
443
  if( hash_parts )
444
  {
445
 
446
    for(i=0;i<directories_size;i++)
447
    {
448
      fprintf(stdout,"%.4s: 0x%X\n",directories[i].type,
449
                hash_part(file,
450
                           directories[i].devOffset, directories[i].len,
451
                           length_header) );
452
    }
453
  }
454
  else
455
  if( extract_all )
456
  {
457
    // User want to extract every part of the dump
458
    for(i=0;i<directories_size;i++)
459
    {
460
      char name[5];
461
 
462
      memcpy(name,directories[i].type,4*sizeof(char));
463
      name[4] = '\0';
464
 
465
      extract_part(file, NULL,
466
                    name,
467
                    directories[i].devOffset, directories[i].len,
468
                    length_header);
469
    }
470
 
471
    fprintf(stdout,"Done.\n");
472
 
473
 
474
  }
475
  else
476
  {
477
    if( list_directories )
478
    {
479
      // User ask a big directory listing 
480
 
481
      for(i=0;i<directories_size; i++)
482
        print_directory_infos( &(directories[i]), 1 );
483
    }
484
    else
485
    { 
486
 
487
      if( extract_is_set )
488
      {
489
        is_found = 0;
490
 
491
        for(i=0;i<directories_size;i++)
492
          if( !strcmp( directories[i].type, str_target ) )
493
          { // Extract & correct part name
494
 
495
            is_found = 1;
496
 
497
            // Lock & load, got name, file, position and length !         
498
            extract_part(file, hExtract,
499
                          directories[i].type,
500
                          directories[i].devOffset, directories[i].len,
501
                          length_header);
502
 
503
          }
504
 
505
        // Wrong part name, error message & small parts list
506
        if(!is_found)
507
        {
508
          fprintf(stderr,"No part named '%s' found.\n\n",str_target);
509
 
510
          for(i=0;i<directories_size;i++)
511
            print_directory_infos( &(directories[i]), 0);  
512
        }
513
 
514
      }
515
      else
516
      { // No actions, small parts list
517
 
518
        for(i=0;i<directories_size;i++)
519
          print_directory_infos( &(directories[i]), 0);   
520
      }
521
 
522
    }
523
  }
524
 
525
  fclose(file);
526
 
527
  free(directories);
528
 
529
  return EXIT_SUCCESS;
530
}