summaryrefslogtreecommitdiff
path: root/utils/mkamsboot/mkamsboot.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/mkamsboot/mkamsboot.c')
-rw-r--r--utils/mkamsboot/mkamsboot.c595
1 files changed, 595 insertions, 0 deletions
diff --git a/utils/mkamsboot/mkamsboot.c b/utils/mkamsboot/mkamsboot.c
new file mode 100644
index 0000000000..09eb15ee43
--- /dev/null
+++ b/utils/mkamsboot/mkamsboot.c
@@ -0,0 +1,595 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * mkamsboot.c - a tool for merging bootloader code into an Sansa V2
11 * (AMS) firmware file
12 *
13 * Copyright (C) 2008 Dave Chapman
14 *
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.
19 *
20 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 * KIND, either express or implied.
22 *
23 ****************************************************************************/
24
25/*
26
27Insert a Rockbox bootloader into a Sansa AMS original firmware file.
28
29Layout of a Sansa AMS original firmware file:
30
31 ---------------------- 0x0
32| HEADER |
33|----------------------| 0x400
34| FIRMWARE BLOCK | (contains the OF version on some fuzev2 firmwares
35|----------------------| 0x600
36| FIRMWARE BLOCK |
37|----------------------| 0x400 + firmware block size
38| LIBRARIES/DATA |
39 ---------------------- END
40
41We replace the main firmware block while preserving the potential OF version
42(bytes 0x600..0x400+firmware_size) as follows:
43
44
45 ---------------------- 0x0
46| |
47| Dual-boot code |
48| |
49|----------------------|
50| EMPTY SPACE |
51|----------------------|
52| |
53| compressed RB image |
54| |
55|----------------------|
56| |
57| compressed OF image |
58| |
59|----------------------|
60| UCL unpack function |
61 ----------------------
62
63This entire block fits into the space previously occupied by the main
64firmware block - the space saved by compressing the OF image is used
65to store the compressed version of the Rockbox bootloader.
66
67On version 1 firmwares, the OF image is typically about 120KB, which allows
68us to store a Rockbox bootloader with an uncompressed size of about 60KB-70KB.
69Version 2 firmwares are bigger and are stored in SDRAM (instead of IRAM).
70In both cases, the RAM we are using is mapped at offset 0x0.
71
72mkamsboot then corrects the checksums and writes a new legal firmware
73file which can be installed on the device.
74
75When the Sansa device boots, this firmware block is loaded to RAM at
76address 0x0 and executed.
77
78Firstly, the dual-boot code will copy the UCL unpack function to the
79end of RAM.
80
81Then, depending on the detection of the dual-boot keypress, either the
82OF image or the Rockbox image is copied to the end of RAM (just before
83the ucl unpack function) and uncompressed to the start of RAM.
84
85Finally, the ucl unpack function branches to address 0x0, passing
86execution to the uncompressed firmware.
87
88
89*/
90
91#include <stdio.h>
92#include <stdlib.h>
93#include <stdint.h>
94#include <sys/types.h>
95#include <sys/stat.h>
96#include <fcntl.h>
97#include <unistd.h>
98#include <string.h>
99
100#include <ucl/ucl.h>
101
102#include "mkamsboot.h"
103
104#include "md5.h"
105
106/* Header for ARM code binaries */
107#include "dualboot.h"
108
109/* Win32 compatibility */
110#ifndef O_BINARY
111#define O_BINARY 0
112#endif
113
114/* fw_revision: version 2 is used in Clipv2, Clip+ and Fuzev2 firmwares */
115/* hw_revision: 4 for m200, 2 for e200/c200, 1 or 2 for fuze/clip, 1 for clip+ */
116const struct ams_models ams_identity[] = {
117 [MODEL_C200V2] = { 2, 1, "c200", dualboot_c200v2, sizeof(dualboot_c200v2), "c2v2", 44 },
118 [MODEL_CLIPPLUS]= { 1, 2, "Clip+", dualboot_clipplus, sizeof(dualboot_clipplus), "cli+", 66 },
119 [MODEL_CLIPV2] = { 2, 2, "Clip", dualboot_clipv2, sizeof(dualboot_clipv2), "clv2", 60 },
120 [MODEL_CLIP] = { 1, 1, "Clip", dualboot_clip, sizeof(dualboot_clip), "clip", 40 },
121 [MODEL_E200V2] = { 2, 1, "e200", dualboot_e200v2, sizeof(dualboot_e200v2), "e2v2", 41 },
122 [MODEL_FUZEV2] = { 2, 2, "Fuze", dualboot_fuzev2, sizeof(dualboot_fuzev2), "fuz2", 68 },
123 [MODEL_FUZE] = { 1, 1, "Fuze", dualboot_fuze, sizeof(dualboot_fuze), "fuze", 43 },
124 [MODEL_M200V4] = { 4, 1, "m200", dualboot_m200v4, sizeof(dualboot_m200v4), "m2v4", 42 },
125 [MODEL_CLIPZIP] = { 1, 2, "ClipZip", dualboot_clipzip, sizeof(dualboot_clipzip), "clzp", 79 },
126};
127
128
129/* Checksums of unmodified original firmwares - for safety, and device
130 detection */
131static struct md5sums sansasums[] = {
132 /* NOTE: Different regional versions of the firmware normally only
133 differ in the filename - the md5sums are identical */
134
135 /* model version md5 */
136 { MODEL_E200V2, "3.01.11", "e622ca8cb6df423f54b8b39628a1f0a3" },
137 { MODEL_E200V2, "3.01.14", "2c1d0383fc3584b2cc83ba8cc2243af6" },
138 { MODEL_E200V2, "3.01.16", "12563ad71b25a1034cf2092d1e0218c4" },
139
140 { MODEL_FUZE, "1.01.11", "cac8ffa03c599330ac02c4d41de66166" },
141 { MODEL_FUZE, "1.01.15", "df0e2c1612727f722c19a3c764cff7f2" },
142 { MODEL_FUZE, "1.01.22", "5aff5486fe8dd64239cc71eac470af98" },
143 { MODEL_FUZE, "1.02.26", "7c632c479461c48c8833baed74eb5e4f" },
144 { MODEL_FUZE, "1.02.28", "5b34260f6470e75f702a9c6825471752" },
145 { MODEL_FUZE, "1.02.31", "66d01b37462a5ef7ccc6ad37188b4235" },
146
147 { MODEL_C200V2, "3.02.05", "b6378ebd720b0ade3fad4dc7ab61c1a5" },
148
149 { MODEL_M200V4, "4.00.45", "82e3194310d1514e3bbcd06e84c4add3" },
150 { MODEL_M200V4, "4.01.08-A", "fc9dd6116001b3e6a150b898f1b091f0" },
151 { MODEL_M200V4, "4.01.08-E", "d3fb7d8ec8624ee65bc99f8dab0e2369" },
152
153 { MODEL_CLIP, "1.01.17", "12caad785d506219d73f538772afd99e" },
154 { MODEL_CLIP, "1.01.18", "d720b266bd5afa38a198986ef0508a45" },
155 { MODEL_CLIP, "1.01.20", "236d8f75189f468462c03f6d292cf2ac" },
156 { MODEL_CLIP, "1.01.29", "c12711342169c66e209540cd1f27cd26" },
157 { MODEL_CLIP, "1.01.30", "f2974d47c536549c9d8259170f1dbe4d" },
158 { MODEL_CLIP, "1.01.32", "d835d12342500732ffb9c4ee54abec15" },
159 { MODEL_CLIP, "1.01.35", "b4d0edb3b8f2a4e8eee0a344f7f8e480" },
160
161 { MODEL_CLIPV2, "2.01.16", "c57fb3fcbe07c2c9b360f060938f80cb" },
162 { MODEL_CLIPV2, "2.01.32", "0ad3723e52022509089d938d0fbbf8c5" },
163 { MODEL_CLIPV2, "2.01.35", "a3cbbd22b9508d7f8a9a1a39acc342c2" },
164
165 { MODEL_CLIPPLUS, "01.02.09", "656d38114774c2001dc18e6726df3c5d" },
166 { MODEL_CLIPPLUS, "01.02.13", "5f89872b79ef440b0e5ee3a7a44328b2" },
167 { MODEL_CLIPPLUS, "01.02.15", "680a4f521e790ad25b93b1b16f3a207d" },
168 { MODEL_CLIPPLUS, "01.02.16", "055a53de1dfb09f6cb71c504ad48bd13" },
169 { MODEL_CLIPPLUS, "01.02.18", "80b547244438b113e2a55ff0305f12c0" },
170
171 { MODEL_FUZEV2, "2.01.17", "8b85fb05bf645d08a4c8c3e344ec9ebe" },
172 { MODEL_FUZEV2, "2.02.26", "d4f6f85c3e4a8ea8f2e5acc421641801" },
173 { MODEL_FUZEV2, "2.03.31", "74fb197ccd51707388f3b233402186a6" },
174 { MODEL_FUZEV2, "2.03.33", "1599cc73d02ea7fe53fe2d4379c24b66" },
175
176 { MODEL_CLIPZIP, "1.01.12", "45adea0873326b5af34f096e5c402f78" },
177 { MODEL_CLIPZIP, "1.01.15", "f62af954334cd9ba1a87a7fa58ec6074" },
178 { MODEL_CLIPZIP, "1.01.17", "27bcb343d6950f35dc261629e22ba60c" },
179 { MODEL_CLIPZIP, "1.01.18", "ef16aa9e02b49885ebede5aa149502e8" },
180 { MODEL_CLIPZIP, "1.01.20", "d88c8977cc6a952d3f51ece105869d97" },
181 { MODEL_CLIPZIP, "1.01.21", "92c814d6e3250189706a36d2b49b6152" },
182};
183
184#define NUM_MD5S (sizeof(sansasums)/sizeof(sansasums[0]))
185
186static unsigned int model_memory_size(int model)
187{
188 /* The OF boots with IRAM (320kB) mapped at 0x0 */
189
190 if(model == MODEL_CLIPV2)
191 {
192 /* The decompressed Clipv2 OF is around 380kB.
193 * Let's use the full IRAM (1MB on AMSv2)
194 */
195 return 1 << 20;
196 }
197 else
198 {
199 /* The IRAM is 320kB on AMSv1, and 320 will be enough on Fuzev1/Clip+ */
200 return 320 << 10;
201 }
202}
203
204int firmware_revision(int model)
205{
206 return ams_identity[model].fw_revision;
207}
208
209static off_t filesize(int fd)
210{
211 struct stat buf;
212
213 if (fstat(fd, &buf) < 0) {
214 perror("[ERR] Checking filesize of input file");
215 return -1;
216 } else {
217 return(buf.st_size);
218 }
219}
220
221static uint32_t get_uint32le(unsigned char* p)
222{
223 return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
224}
225
226static uint32_t get_uint32be(unsigned char* p)
227{
228 return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
229}
230
231static void put_uint32le(unsigned char* p, uint32_t x)
232{
233 p[0] = x & 0xff;
234 p[1] = (x >> 8) & 0xff;
235 p[2] = (x >> 16) & 0xff;
236 p[3] = (x >> 24) & 0xff;
237}
238
239void calc_MD5(unsigned char* buf, int len, char *md5str)
240{
241 int i;
242 md5_context ctx;
243 unsigned char md5sum[16];
244
245 md5_starts(&ctx);
246 md5_update(&ctx, buf, len);
247 md5_finish(&ctx, md5sum);
248
249 for (i = 0; i < 16; ++i)
250 sprintf(md5str + 2*i, "%02x", md5sum[i]);
251}
252
253/* Calculate a simple checksum used in Sansa Original Firmwares */
254static uint32_t calc_checksum(unsigned char* buf, uint32_t n)
255{
256 uint32_t sum = 0;
257 uint32_t i;
258
259 for (i=0;i<n;i+=4)
260 sum += get_uint32le(buf + i);
261
262 return sum;
263}
264
265/* Compress using nrv2e algorithm : Thumb decompressor fits in 168 bytes ! */
266static unsigned char* uclpack(unsigned char* inbuf, int insize, int* outsize)
267{
268 int maxsize;
269 unsigned char* outbuf;
270 int r;
271
272 /* The following formula comes from the UCL documentation */
273 maxsize = insize + (insize / 8) + 256;
274
275 /* Allocate some memory for the output buffer */
276 outbuf = malloc(maxsize);
277
278 if (outbuf == NULL)
279 return NULL;
280
281 r = ucl_nrv2e_99_compress(
282 (const ucl_bytep) inbuf,
283 (ucl_uint) insize,
284 (ucl_bytep) outbuf,
285 (ucl_uintp) outsize,
286 0, 10, NULL, NULL);
287
288 if (r != UCL_E_OK || *outsize > maxsize) {
289 /* this should NEVER happen, and implies memory corruption */
290 fprintf(stderr, "internal error - compression failed: %d\n", r);
291 free(outbuf);
292 return NULL;
293 }
294
295 return outbuf;
296}
297
298#define ERROR(format, ...) \
299 do { \
300 snprintf(errstr, errstrsize, format, __VA_ARGS__); \
301 goto error; \
302 } while(0)
303
304/* Loads a Sansa AMS Original Firmware file into memory */
305unsigned char* load_of_file(
306 char* filename, int model, off_t* bufsize, struct md5sums *sum,
307 int* firmware_size, unsigned char** of_packed,
308 int* of_packedsize, char* errstr, int errstrsize)
309{
310 int fd;
311 unsigned char* buf =NULL;
312 off_t n;
313 unsigned int i=0;
314 uint32_t checksum;
315 unsigned int last_word;
316
317 fd = open(filename, O_RDONLY|O_BINARY);
318 if (fd < 0)
319 ERROR("[ERR] Could not open %s for reading\n", filename);
320
321 *bufsize = filesize(fd);
322
323 buf = malloc(*bufsize);
324 if (buf == NULL)
325 ERROR("[ERR] Could not allocate memory for %s\n", filename);
326
327 n = read(fd, buf, *bufsize);
328
329 if (n != *bufsize)
330 ERROR("[ERR] Could not read file %s\n", filename);
331
332 /* check the file */
333
334 /* Calculate MD5 checksum of OF */
335 calc_MD5(buf, *bufsize, sum->md5);
336
337 while ((i < NUM_MD5S) && (strcmp(sansasums[i].md5, sum->md5) != 0))
338 i++;
339
340 if (i < NUM_MD5S) {
341 *sum = sansasums[i];
342 if(sum->model != model) {
343 ERROR("[ERR] OF File provided is %sv%d version %s, not for %sv%d\n",
344 ams_identity[sum->model].model_name, ams_identity[sum->model].hw_revision,
345 sum->version, ams_identity[model].model_name, ams_identity[model].hw_revision
346 );
347 }
348 } else {
349 /* OF unknown, give a list of tested versions for the requested model */
350
351 char tested_versions[100];
352 tested_versions[0] = '\0';
353
354 for (i = 0; i < NUM_MD5S ; i++)
355 if (sansasums[i].model == model) {
356 if (tested_versions[0] != '\0') {
357 strncat(tested_versions, ", ",
358 sizeof(tested_versions) - strlen(tested_versions) - 1);
359 }
360 strncat(tested_versions, sansasums[i].version,
361 sizeof(tested_versions) - strlen(tested_versions) - 1);
362 }
363
364 ERROR("[ERR] Original firmware unknown, please try another version."
365 " Tested %sv%d versions are: %s\n",
366 ams_identity[model].model_name, ams_identity[model].hw_revision, tested_versions);
367 }
368
369 /* TODO: Do some more sanity checks on the OF image. Some images (like
370 m200v4) dont have a checksum at the end, only padding (0xdeadbeef). */
371 last_word = *bufsize - 4;
372 checksum = get_uint32le(buf + last_word);
373 if (checksum != 0xefbeadde && checksum != calc_checksum(buf, last_word))
374 ERROR("%s", "[ERR] Whole file checksum failed\n");
375
376 if (ams_identity[sum->model].bootloader == NULL)
377 ERROR("[ERR] Unsupported model - \"%s\"\n", ams_identity[sum->model].model_name);
378
379 /* Get the firmware size */
380 if (ams_identity[sum->model].fw_revision == 1)
381 *firmware_size = get_uint32le(&buf[0x0c]);
382 else if (ams_identity[sum->model].fw_revision == 2)
383 *firmware_size = get_uint32le(&buf[0x10]);
384
385 /* Compress the original firmware image */
386 *of_packed = uclpack(buf + 0x400, *firmware_size, of_packedsize);
387 if (*of_packed == NULL)
388 ERROR("[ERR] Could not compress %s\n", filename);
389
390 return buf;
391
392error:
393 free(buf);
394 return NULL;
395}
396
397/* Loads a rockbox bootloader file into memory */
398unsigned char* load_rockbox_file(
399 char* filename, int *model, int* bufsize, int* rb_packedsize,
400 char* errstr, int errstrsize)
401{
402 int fd;
403 unsigned char* buf = NULL;
404 unsigned char* packed = NULL;
405 unsigned char header[8];
406 uint32_t sum;
407 off_t n;
408 int i;
409
410 fd = open(filename, O_RDONLY|O_BINARY);
411 if (fd < 0)
412 ERROR("[ERR] Could not open %s for reading\n", filename);
413
414 /* Read Rockbox header */
415 n = read(fd, header, sizeof(header));
416 if (n != sizeof(header))
417 ERROR("[ERR] Could not read file %s\n", filename);
418
419 for(*model = 0; *model < NUM_MODELS; (*model)++)
420 if (memcmp(ams_identity[*model].rb_model_name, header + 4, 4) == 0)
421 break;
422
423 if(*model == NUM_MODELS)
424 ERROR("[ERR] Model name \"%4.4s\" unknown. Is this really a rockbox bootloader?\n", header + 4);
425
426 *bufsize = filesize(fd) - sizeof(header);
427
428 buf = malloc(*bufsize);
429 if (buf == NULL)
430 ERROR("[ERR] Could not allocate memory for %s\n", filename);
431
432 n = read(fd, buf, *bufsize);
433
434 if (n != *bufsize)
435 ERROR("[ERR] Could not read file %s\n", filename);
436
437 /* Check checksum */
438 sum = ams_identity[*model].rb_model_num;
439 for (i = 0; i < *bufsize; i++) {
440 /* add 8 unsigned bits but keep a 32 bit sum */
441 sum += buf[i];
442 }
443
444 if (sum != get_uint32be(header))
445 ERROR("[ERR] Checksum mismatch in %s\n", filename);
446
447 packed = uclpack(buf, *bufsize, rb_packedsize);
448 if(packed == NULL)
449 ERROR("[ERR] Could not compress %s\n", filename);
450
451 free(buf);
452 return packed;
453
454error:
455 free(buf);
456 return NULL;
457}
458
459#undef ERROR
460
461/* Patches a Sansa AMS Original Firmware file */
462void patch_firmware(
463 int model, int fw_revision, int firmware_size, unsigned char* buf,
464 int len, unsigned char* of_packed, int of_packedsize,
465 unsigned char* rb_packed, int rb_packedsize)
466{
467 unsigned char *p;
468 uint32_t sum, filesum;
469 uint32_t ucl_dest;
470 unsigned int i;
471
472 /* Zero the original firmware area - not needed, but helps debugging */
473 memset(buf + 0x600, 0, firmware_size);
474
475 /* Insert dual-boot bootloader at offset 0x200, we preserve the OF
476 * version string located between 0x0 and 0x200 */
477 memcpy(buf + 0x600, ams_identity[model].bootloader, ams_identity[model].bootloader_size);
478
479 /* Insert vectors, they won't overwrite the OF version string */
480 static const uint32_t goto_start = 0xe3a0fc02; // mov pc, #0x200
481 static const uint32_t infinite_loop = 0xeafffffe; // 1: b 1b
482 /* ALL vectors: infinite loop */
483 for (i=0; i < 8; i++)
484 put_uint32le(buf + 0x400 + 4*i, infinite_loop);
485 /* Now change only the interesting vectors */
486 /* Reset/SWI vectors: branch to our dualboot code at 0x200 */
487 put_uint32le(buf + 0x400 + 4*0, goto_start); // Reset
488 put_uint32le(buf + 0x400 + 4*2, goto_start); // SWI
489
490 /* We are filling the firmware buffer backwards from the end */
491 p = buf + 0x400 + firmware_size;
492
493 /* 1 - UCL unpack function */
494 p -= sizeof(nrv2e_d8);
495 memcpy(p, nrv2e_d8, sizeof(nrv2e_d8));
496
497 /* 2 - Compressed copy of original firmware */
498 p -= of_packedsize;
499 memcpy(p, of_packed, of_packedsize);
500
501 /* 3 - Compressed copy of Rockbox bootloader */
502 p -= rb_packedsize;
503 memcpy(p, rb_packed, rb_packedsize);
504
505 /* Write the locations of the various images to the variables at the
506 start of the dualboot image - we save the location of the last byte
507 in each image, along with the size in bytes */
508
509 /* UCL unpack function */
510 put_uint32le(&buf[0x604], firmware_size - 1);
511 put_uint32le(&buf[0x608], sizeof(nrv2e_d8));
512
513 /* Compressed original firmware image */
514 put_uint32le(&buf[0x60c], firmware_size - sizeof(nrv2e_d8) - 1);
515 put_uint32le(&buf[0x610], of_packedsize);
516
517 /* Compressed Rockbox image */
518 put_uint32le(&buf[0x614], firmware_size - sizeof(nrv2e_d8) - of_packedsize
519 - 1);
520 put_uint32le(&buf[0x618], rb_packedsize);
521
522 ucl_dest = model_memory_size(model) - 1; /* last byte of memory */
523 put_uint32le(&buf[0x61c], ucl_dest);
524
525 /* Update the firmware block checksum */
526 sum = calc_checksum(buf + 0x400, firmware_size);
527
528 if (fw_revision == 1) {
529 put_uint32le(&buf[0x04], sum);
530 put_uint32le(&buf[0x204], sum);
531 } else if (fw_revision == 2) {
532 put_uint32le(&buf[0x08], sum);
533 put_uint32le(&buf[0x208], sum);
534
535 /* Update the header checksums */
536 put_uint32le(&buf[0x1fc], calc_checksum(buf, 0x1fc));
537 put_uint32le(&buf[0x3fc], calc_checksum(buf + 0x200, 0x1fc));
538 }
539
540 /* Update the whole-file checksum */
541 filesum = 0;
542 for (i=0;i < (unsigned)len - 4; i+=4)
543 filesum += get_uint32le(&buf[i]);
544
545 put_uint32le(buf + len - 4, filesum);
546}
547
548/* returns != 0 if the firmware can be safely patched */
549int check_sizes(int model, int rb_packed_size, int rb_unpacked_size,
550 int of_packed_size, int of_unpacked_size, int *total_size,
551 char *errstr, int errstrsize)
552{
553 /* XXX: we keep the first 0x200 bytes block unmodified, we just replace
554 * the ARM vectors */
555 unsigned int packed_size = ams_identity[model].bootloader_size + sizeof(nrv2e_d8) +
556 of_packed_size + rb_packed_size + 0x200;
557
558 /* how much memory is available */
559 unsigned int memory_size = model_memory_size(model);
560
561 /* the memory used when unpacking the OF */
562 unsigned int ram_of = sizeof(nrv2e_d8) + of_packed_size + of_unpacked_size;
563
564 /* the memory used when unpacking the bootloader */
565 unsigned int ram_rb = sizeof(nrv2e_d8) + rb_packed_size + rb_unpacked_size;
566
567 *total_size = packed_size;
568
569#define ERROR(format, ...) \
570 do { \
571 snprintf(errstr, errstrsize, format, __VA_ARGS__); \
572 return 0; \
573 } while(0)
574
575 /* will packed data fit in the OF file ? */
576 if(packed_size > of_unpacked_size)
577 ERROR(
578 "[ERR] Packed data (%d bytes) doesn't fit in the firmware "
579 "(%d bytes)\n", packed_size, of_unpacked_size
580 );
581
582 else if(ram_rb > memory_size)
583 ERROR("[ERR] Rockbox can't be unpacked at runtime, needs %d bytes "
584 "of memory and only %d available\n", ram_rb, memory_size
585 );
586
587 else if(ram_of > memory_size)
588 ERROR("[ERR] OF can't be unpacked at runtime, needs %d bytes "
589 "of memory and only %d available\n", ram_of, memory_size
590 );
591
592 return 1;
593
594#undef ERROR
595}