Add Sansa Clip+ target to test mkamsboot
[kugel-rb.git] / rbutil / mkamsboot / mkamsboot.c
blobd983d378531941904768b9c3d6e10e660750b872
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * mkamsboot.c - a tool for merging bootloader code into an Sansa V2
11 * (AMS) firmware file
13 * Copyright (C) 2008 Dave Chapman
15 * This program is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU General Public License
17 * as published by the Free Software Foundation; either version 2
18 * of the License, or (at your option) any later version.
20 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 * KIND, either express or implied.
23 ****************************************************************************/
27 Insert a Rockbox bootloader into a Sansa AMS original firmware file.
29 Layout of a Sansa AMS original firmware file:
31 ---------------------- 0x0
32 | HEADER |
33 |----------------------| 0x400
34 | FIRMWARE BLOCK |
35 |----------------------| 0x400 + firmware block size
36 | LIBRARIES/DATA |
37 ---------------------- END
39 We replace the main firmware block (bytes 0x400..0x400+firmware_size)
40 as follows:
43 ---------------------- 0x0
44 | |
45 | Dual-boot code |
46 | |
47 |----------------------|
48 | EMPTY SPACE |
49 |----------------------|
50 | |
51 | compressed RB image |
52 | |
53 |----------------------|
54 | |
55 | compressed OF image |
56 | |
57 |----------------------|
58 | UCL unpack function |
59 ----------------------
61 This entire block fits into the space previously occupied by the main
62 firmware block - the space saved by compressing the OF image is used
63 to store the compressed version of the Rockbox bootloader.
65 On version 1 firmwares, the OF image is typically about 120KB, which allows
66 us to store a Rockbox bootloader with an uncompressed size of about 60KB-70KB.
67 Version 2 firmwares are bigger and are stored in SDRAM (instead of IRAM).
68 In both cases, the RAM we are using is mapped at offset 0x0.
70 mkamsboot then corrects the checksums and writes a new legal firmware
71 file which can be installed on the device.
73 When the Sansa device boots, this firmware block is loaded to RAM at
74 address 0x0 and executed.
76 Firstly, the dual-boot code will copy the UCL unpack function to the
77 end of RAM.
79 Then, depending on the detection of the dual-boot keypress, either the
80 OF image or the Rockbox image is copied to the end of RAM (just before
81 the ucl unpack function) and uncompressed to the start of RAM.
83 Finally, the ucl unpack function branches to address 0x0, passing
84 execution to the uncompressed firmware.
89 #include <stdio.h>
90 #include <stdlib.h>
91 #include <stdint.h>
92 #include <sys/types.h>
93 #include <sys/stat.h>
94 #include <fcntl.h>
95 #include <unistd.h>
96 #include <string.h>
98 #include <ucl/ucl.h>
100 #include "mkamsboot.h"
102 #include "md5.h"
104 /* Header for ARM code binaries */
105 #include "dualboot.h"
107 /* Win32 compatibility */
108 #ifndef O_BINARY
109 #define O_BINARY 0
110 #endif
112 /* 4 for m200, 2 for e200/c200, 1 or 2 for fuze/clip, 1 for clip+ */
113 const unsigned short hw_revisions[] = {
114 [MODEL_FUZE] = 1,
115 [MODEL_CLIP] = 1,
116 [MODEL_CLIPV2] = 2,
117 [MODEL_E200V2] = 2,
118 [MODEL_M200V4] = 4,
119 [MODEL_C200V2] = 2,
120 [MODEL_CLIPPLUS]= 1,
123 /* version 2 is used in Clipv2, Clip+ and Fuzev2 firmwares */
124 const unsigned short fw_revisions[] = {
125 [MODEL_FUZE] = 1,
126 [MODEL_CLIP] = 1,
127 [MODEL_CLIPV2] = 2,
128 [MODEL_E200V2] = 1,
129 [MODEL_M200V4] = 1,
130 [MODEL_C200V2] = 1,
131 [MODEL_CLIPPLUS]= 2,
134 /* Descriptive name of these models */
135 const char* model_names[] = {
136 [MODEL_FUZE] = "Fuze",
137 [MODEL_CLIP] = "Clip",
138 [MODEL_CLIPV2] = "Clip",
139 [MODEL_CLIPPLUS]= "Clip+",
140 [MODEL_E200V2] = "e200",
141 [MODEL_M200V4] = "m200",
142 [MODEL_C200V2] = "c200",
145 /* Dualboot functions for these models */
146 static const unsigned char* bootloaders[] = {
147 [MODEL_FUZE] = dualboot_fuze,
148 [MODEL_CLIP] = dualboot_clip,
149 [MODEL_CLIPV2] = dualboot_clipv2,
150 [MODEL_E200V2] = dualboot_e200v2,
151 [MODEL_M200V4] = dualboot_m200v4,
152 [MODEL_C200V2] = dualboot_c200v2,
153 [MODEL_CLIPPLUS]= dualboot_clipplus,
156 /* Size of dualboot functions for these models */
157 const int bootloader_sizes[] = {
158 [MODEL_FUZE] = sizeof(dualboot_fuze),
159 [MODEL_CLIP] = sizeof(dualboot_clip),
160 [MODEL_CLIPV2] = sizeof(dualboot_clipv2),
161 [MODEL_E200V2] = sizeof(dualboot_e200v2),
162 [MODEL_M200V4] = sizeof(dualboot_m200v4),
163 [MODEL_C200V2] = sizeof(dualboot_c200v2),
164 [MODEL_CLIPPLUS]= sizeof(dualboot_clipplus),
167 /* Model names used in the Rockbox header in ".sansa" files - these match the
168 -add parameter to the "scramble" tool */
169 static const char* rb_model_names[] = {
170 [MODEL_FUZE] = "fuze",
171 [MODEL_CLIP] = "clip",
172 [MODEL_CLIPV2] = "clv2",
173 [MODEL_E200V2] = "e2v2",
174 [MODEL_M200V4] = "m2v4",
175 [MODEL_C200V2] = "c2v2",
176 [MODEL_CLIPPLUS]= "cli+",
179 /* Model numbers used to initialise the checksum in the Rockbox header in
180 ".sansa" files - these are the same as MODEL_NUMBER in config-target.h */
181 static const int rb_model_num[] = {
182 [MODEL_FUZE] = 43,
183 [MODEL_CLIP] = 40,
184 [MODEL_CLIPV2] = 60,
185 [MODEL_E200V2] = 41,
186 [MODEL_M200V4] = 42,
187 [MODEL_C200V2] = 44,
188 [MODEL_CLIPPLUS]= 66,
191 /* Checksums of unmodified original firmwares - for safety, and device
192 detection */
193 static struct md5sums sansasums[] = {
194 /* NOTE: Different regional versions of the firmware normally only
195 differ in the filename - the md5sums are identical */
197 /* model version md5 */
198 { MODEL_E200V2, "3.01.11", "e622ca8cb6df423f54b8b39628a1f0a3" },
199 { MODEL_E200V2, "3.01.14", "2c1d0383fc3584b2cc83ba8cc2243af6" },
200 { MODEL_E200V2, "3.01.16", "12563ad71b25a1034cf2092d1e0218c4" },
202 { MODEL_FUZE, "1.01.11", "cac8ffa03c599330ac02c4d41de66166" },
203 { MODEL_FUZE, "1.01.15", "df0e2c1612727f722c19a3c764cff7f2" },
204 { MODEL_FUZE, "1.01.22", "5aff5486fe8dd64239cc71eac470af98" },
205 { MODEL_FUZE, "1.02.26", "7c632c479461c48c8833baed74eb5e4f" },
206 { MODEL_FUZE, "1.02.28", "5b34260f6470e75f702a9c6825471752" },
208 { MODEL_C200V2, "3.02.05", "b6378ebd720b0ade3fad4dc7ab61c1a5" },
210 { MODEL_M200V4, "4.00.45", "82e3194310d1514e3bbcd06e84c4add3" },
211 { MODEL_M200V4, "4.01.08-A", "fc9dd6116001b3e6a150b898f1b091f0" },
212 { MODEL_M200V4, "4.01.08-E", "d3fb7d8ec8624ee65bc99f8dab0e2369" },
214 { MODEL_CLIP, "1.01.17", "12caad785d506219d73f538772afd99e" },
215 { MODEL_CLIP, "1.01.18", "d720b266bd5afa38a198986ef0508a45" },
216 { MODEL_CLIP, "1.01.20", "236d8f75189f468462c03f6d292cf2ac" },
217 { MODEL_CLIP, "1.01.29", "c12711342169c66e209540cd1f27cd26" },
218 { MODEL_CLIP, "1.01.30", "f2974d47c536549c9d8259170f1dbe4d" },
219 { MODEL_CLIP, "1.01.32", "d835d12342500732ffb9c4ee54abec15" },
221 { MODEL_CLIPV2, "2.01.16", "c57fb3fcbe07c2c9b360f060938f80cb" },
222 { MODEL_CLIPV2, "2.01.32", "0ad3723e52022509089d938d0fbbf8c5" },
224 #if 0 /* uncomment when Clip+ support is tested */
225 { MODEL_CLIPPLUS, "01.02.09", "656d38114774c2001dc18e6726df3c5d" },
226 #endif
229 #define NUM_MD5S (sizeof(sansasums)/sizeof(sansasums[0]))
231 int firmware_revision(int model)
233 return fw_revisions[model];
236 static off_t filesize(int fd)
238 struct stat buf;
240 if (fstat(fd, &buf) < 0) {
241 perror("[ERR] Checking filesize of input file");
242 return -1;
243 } else {
244 return(buf.st_size);
248 static uint32_t get_uint32le(unsigned char* p)
250 return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
253 static uint32_t get_uint32be(unsigned char* p)
255 return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
258 static void put_uint32le(unsigned char* p, uint32_t x)
260 p[0] = x & 0xff;
261 p[1] = (x >> 8) & 0xff;
262 p[2] = (x >> 16) & 0xff;
263 p[3] = (x >> 24) & 0xff;
266 void calc_MD5(unsigned char* buf, int len, char *md5str)
268 int i;
269 md5_context ctx;
270 unsigned char md5sum[16];
272 md5_starts(&ctx);
273 md5_update(&ctx, buf, len);
274 md5_finish(&ctx, md5sum);
276 for (i = 0; i < 16; ++i)
277 sprintf(md5str + 2*i, "%02x", md5sum[i]);
280 /* Calculate a simple checksum used in Sansa Original Firmwares */
281 static uint32_t calc_checksum(unsigned char* buf, uint32_t n)
283 uint32_t sum = 0;
284 uint32_t i;
286 for (i=0;i<n;i+=4)
287 sum += get_uint32le(buf + i);
289 return sum;
292 static int get_model(int model_id)
294 switch(model_id) {
295 case 0x1e:
296 return MODEL_FUZE;
297 case 0x22:
298 return MODEL_CLIP;
299 case 0x23:
300 return MODEL_C200V2;
301 case 0x24:
302 return MODEL_E200V2;
303 case 0x25:
304 return MODEL_M200V4;
305 case 0x27:
306 return MODEL_CLIPV2;
307 case 0x28:
308 return MODEL_CLIPPLUS;
311 return MODEL_UNKNOWN;
314 /* Compress using nrv2e algorithm : Thumb decompressor fits in 168 bytes ! */
315 static unsigned char* uclpack(unsigned char* inbuf, int insize, int* outsize)
317 int maxsize;
318 unsigned char* outbuf;
319 int r;
321 /* The following formula comes from the UCL documentation */
322 maxsize = insize + (insize / 8) + 256;
324 /* Allocate some memory for the output buffer */
325 outbuf = malloc(maxsize);
327 if (outbuf == NULL)
328 return NULL;
330 r = ucl_nrv2e_99_compress(
331 (const ucl_bytep) inbuf,
332 (ucl_uint) insize,
333 (ucl_bytep) outbuf,
334 (ucl_uintp) outsize,
335 0, 10, NULL, NULL);
337 if (r != UCL_E_OK || *outsize > maxsize) {
338 /* this should NEVER happen, and implies memory corruption */
339 fprintf(stderr, "internal error - compression failed: %d\n", r);
340 free(outbuf);
341 return NULL;
344 return outbuf;
347 #define ERROR(format, ...) \
348 do { \
349 snprintf(errstr, errstrsize, format, __VA_ARGS__); \
350 goto error; \
351 } while(0)
353 /* Loads a Sansa AMS Original Firmware file into memory */
354 unsigned char* load_of_file(
355 char* filename, off_t* bufsize, struct md5sums *sum,
356 int* firmware_size, unsigned char** of_packed,
357 int* of_packedsize, char* errstr, int errstrsize)
359 int fd;
360 unsigned char* buf =NULL;
361 off_t n;
362 unsigned int i=0;
363 uint32_t checksum;
364 int model_id;
365 unsigned int last_word;
367 fd = open(filename, O_RDONLY|O_BINARY);
368 if (fd < 0)
369 ERROR("[ERR] Could not open %s for reading\n", filename);
371 *bufsize = filesize(fd);
373 buf = malloc(*bufsize);
374 if (buf == NULL)
375 ERROR("[ERR] Could not allocate memory for %s\n", filename);
377 n = read(fd, buf, *bufsize);
379 if (n != *bufsize)
380 ERROR("[ERR] Could not read file %s\n", filename);
382 /* check the file */
384 /* Calculate MD5 checksum of OF */
385 calc_MD5(buf, *bufsize, sum->md5);
387 while ((i < NUM_MD5S) && (strcmp(sansasums[i].md5, sum->md5) != 0))
388 i++;
390 if (i < NUM_MD5S) {
391 *sum = sansasums[i];
392 } else {
393 int fw_version = (get_uint32le(&buf[0x204]) == 0x0000f000) ? 2 : 1;
394 model_id = buf[(fw_version == 2) ? 0x219 : 0x215];
395 sum->model = get_model(model_id);
397 if (sum->model == MODEL_UNKNOWN)
398 ERROR("[ERR] Unknown firmware model (v%d) - model id 0x%02x\n",
399 fw_version, model_id);
401 #if 1 /* comment to test new OFs */
402 char tested_versions[100];
403 tested_versions[0] = '\0';
405 for (i = 0; i < NUM_MD5S ; i++)
406 if (sansasums[i].model == sum->model) {
407 if (tested_versions[0] != '\0') {
408 strncat(tested_versions, ", ",
409 sizeof(tested_versions) - strlen(tested_versions) - 1);
411 strncat(tested_versions, sansasums[i].version,
412 sizeof(tested_versions) - strlen(tested_versions) - 1);
415 ERROR("[ERR] Original firmware unknown, please try an other version." \
416 " Tested %s versions are : %s\n",
417 model_names[sum->model], tested_versions);
418 #endif
421 /* TODO: Do some more sanity checks on the OF image. Some images (like
422 m200v4) dont have a checksum at the end, only padding (0xdeadbeef). */
423 last_word = *bufsize - 4;
424 checksum = get_uint32le(buf + last_word);
425 if (checksum != 0xefbeadde && checksum != calc_checksum(buf, last_word))
426 ERROR("%s", "[ERR] Whole file checksum failed\n");
428 if (bootloaders[sum->model] == NULL)
429 ERROR("[ERR] Unsupported model - \"%s\"\n", model_names[sum->model]);
431 /* Get the firmware size */
432 if (fw_revisions[sum->model] == 1)
433 *firmware_size = get_uint32le(&buf[0x0c]);
434 else if (fw_revisions[sum->model] == 2)
435 *firmware_size = get_uint32le(&buf[0x10]);
437 /* Compress the original firmware image */
438 *of_packed = uclpack(buf + 0x400, *firmware_size, of_packedsize);
439 if (*of_packed == NULL)
440 ERROR("[ERR] Could not compress %s\n", filename);
442 return buf;
444 error:
445 free(buf);
446 return NULL;
449 /* Loads a rockbox bootloader file into memory */
450 unsigned char* load_rockbox_file(
451 char* filename, int model, int* bufsize, int* rb_packedsize,
452 char* errstr, int errstrsize)
454 int fd;
455 unsigned char* buf = NULL;
456 unsigned char* packed = NULL;
457 unsigned char header[8];
458 uint32_t sum;
459 off_t n;
460 int i;
462 fd = open(filename, O_RDONLY|O_BINARY);
463 if (fd < 0)
464 ERROR("[ERR] Could not open %s for reading\n", filename);
466 /* Read Rockbox header */
467 n = read(fd, header, sizeof(header));
468 if (n != sizeof(header))
469 ERROR("[ERR] Could not read file %s\n", filename);
471 /* Check for correct model string */
472 if (memcmp(rb_model_names[model], header + 4, 4)!=0)
473 ERROR("[ERR] Model name \"%s\" not found in %s\n",
474 rb_model_names[model], filename);
476 *bufsize = filesize(fd) - sizeof(header);
478 buf = malloc(*bufsize);
479 if (buf == NULL)
480 ERROR("[ERR] Could not allocate memory for %s\n", filename);
482 n = read(fd, buf, *bufsize);
484 if (n != *bufsize)
485 ERROR("[ERR] Could not read file %s\n", filename);
487 /* Check checksum */
488 sum = rb_model_num[model];
489 for (i = 0; i < *bufsize; i++) {
490 /* add 8 unsigned bits but keep a 32 bit sum */
491 sum += buf[i];
494 if (sum != get_uint32be(header))
495 ERROR("[ERR] Checksum mismatch in %s\n", filename);
497 packed = uclpack(buf, *bufsize, rb_packedsize);
498 if(packed == NULL)
499 ERROR("[ERR] Could not compress %s\n", filename);
501 free(buf);
502 return packed;
504 error:
505 free(buf);
506 return NULL;
509 #undef ERROR
511 /* Patches a Sansa AMS Original Firmware file */
512 void patch_firmware(
513 int model, int fw_revision, int firmware_size, unsigned char* buf,
514 int len, unsigned char* of_packed, int of_packedsize,
515 unsigned char* rb_packed, int rb_packedsize)
517 unsigned char *p;
518 uint32_t sum, filesum;
519 unsigned int i;
521 /* Zero the original firmware area - not needed, but helps debugging */
522 memset(buf + 0x400, 0, firmware_size);
524 /* Insert dual-boot bootloader at offset 0 */
525 memcpy(buf + 0x400, bootloaders[model], bootloader_sizes[model]);
527 /* We are filling the firmware buffer backwards from the end */
528 p = buf + 0x400 + firmware_size;
530 /* 1 - UCL unpack function */
531 p -= sizeof(nrv2e_d8);
532 memcpy(p, nrv2e_d8, sizeof(nrv2e_d8));
534 /* 2 - Compressed copy of original firmware */
535 p -= of_packedsize;
536 memcpy(p, of_packed, of_packedsize);
538 /* 3 - Compressed copy of Rockbox bootloader */
539 p -= rb_packedsize;
540 memcpy(p, rb_packed, rb_packedsize);
542 /* Write the locations of the various images to the variables at the
543 start of the dualboot image - we save the location of the last byte
544 in each image, along with the size in bytes */
546 /* UCL unpack function */
547 put_uint32le(&buf[0x420], firmware_size - 1);
548 put_uint32le(&buf[0x424], sizeof(nrv2e_d8));
550 /* Compressed original firmware image */
551 put_uint32le(&buf[0x428], firmware_size - sizeof(nrv2e_d8) - 1);
552 put_uint32le(&buf[0x42c], of_packedsize);
554 /* Compressed Rockbox image */
555 put_uint32le(&buf[0x430], firmware_size - sizeof(nrv2e_d8) - of_packedsize
556 - 1);
557 put_uint32le(&buf[0x434], rb_packedsize);
560 /* Update the firmware block checksum */
561 sum = calc_checksum(buf + 0x400, firmware_size);
563 if (fw_revision == 1) {
564 put_uint32le(&buf[0x04], sum);
565 put_uint32le(&buf[0x204], sum);
566 } else if (fw_revision == 2) {
567 put_uint32le(&buf[0x08], sum);
568 put_uint32le(&buf[0x208], sum);
570 /* Update the header checksums */
571 put_uint32le(&buf[0x1fc], calc_checksum(buf, 0x1fc));
572 put_uint32le(&buf[0x3fc], calc_checksum(buf + 0x200, 0x1fc));
575 /* Update the whole-file checksum */
576 filesum = 0;
577 for (i=0;i < (unsigned)len - 4; i+=4)
578 filesum += get_uint32le(&buf[i]);
580 put_uint32le(buf + len - 4, filesum);
583 /* returns size of new firmware block */
584 int total_size(int model, int rb_packedsize, int of_packedsize)
586 return bootloader_sizes[model] + sizeof(nrv2e_d8) + of_packedsize +
587 rb_packedsize;