rockbox/utils/mks5lboot/main.c
Andrew Rice aed1945c5d mks5lboot: support the iPod Nano 3G
Registers the Nano 3G (platform ipodnano3g, model number 117, "nn3g"
header) so mks5lboot can build DFU installers and uninstallers for it,
adds its original bootloader to the dualboot code, and lists the
platform in the usage text and the README.

The target has to be named ipodnano3g rather than nano3g: the dualboot
Makefile derives the source directory and the piezo driver's file name
from it.

The per-target OF hash table is the substantive change. identify_fw()
decrypts the IM3 header's data_sign with the hardware UKEY and looks it
up in of_sha[], and anything not listed is taken to be a Rockbox
bootloader. The table held only iPod Classic firmware, so on a Nano 3G
the installer took Apple's own bootloader for a Rockbox one and gave up,
and the uninstaller would have refused to restore it. Both bail out
before writing, so nothing is damaged, but neither can work. The table
is now per target, and lists the bootloader of every Nano 3G firmware
release, 1.0.1 to 1.1.3.

The decrypted data_sign is the first 16 bytes of the SHA-1 of the
plaintext bootloader, so it is the same on every unit. Each release's
updater image (aupd, GID-encrypted, in the ipsw) carries that bootloader,
0x1f800 bytes, at the start of a NOR image. The aupd of each release was
decrypted on a Nano 3G with the hardware GID key and hashed on the host.
For 1.1.3 the result matches the data_sign read from a 4GB unit (model
MA978) whose NOR had never been written to, and the bootloader in its
aupd is byte-identical to the decrypted copy the installer relocated on
that unit. 1.1.2 and 1.1.3 ship the same bootloader.

dualboot.c is generated, and only the Nano 3G arrays are added. The iPod
Classic arrays are left byte-for-byte as they were: rebuilding them with
a different compiler changes their bytes, which would ship an untested
installer to Classic users for no reason.

The dualboot Makefile did not build from the current tree for any
target, which the committed blobs, older than both problems, had hidden.
config.h needs autoconf.h, which tools/configure generates per target,
so each target now takes a CONFIGDIR_<target> pointing at a configured
bootloader build for it, e.g.

  make CONFIGDIR_ipod6g=../../../build-ipod6g-bl \
       CONFIGDIR_ipodnano3g=../../../build-nano3g-bl

And the linker script is preprocessed with __ASSEMBLER__ defined, under
which config.h now emits the ldmpc/ldrpc assembler macros that ld
rejects; the sed that cleans it now also drops .macro, .endm and .syntax
lines and the macro bodies. Before these fixes the iPod Classic build
failed first on the missing autoconf.h and then with a linker syntax
error; with them it builds both blobs.

Tested on that unit, with s5l8702pwnage delivering the images through
Apple's DFU, and again with mks5lboot's own --bl-inst and --bl-uninst:
the installer put Rockbox in NOR, the unit then booted
Rockbox, and holding MENU booted Apple's firmware from the relocated
original bootloader; the uninstaller restored it and the unit booted
Apple's firmware again. Those images carried a table holding only the
1.1.3 entry. The Nano 3G blobs in dualboot.c were then rebuilt with the
Makefile for the full table - with the one-entry table the rebuild was
byte-identical to what the tested images carried - and mks5lboot
--bl-inst with them installed Rockbox on the same unit, which booted
Rockbox and, holding MENU, Apple's firmware. The uninstaller built with
the full table has not been run, and no firmware other than 1.1.3 has
been installed to or uninstalled from on hardware. For the iPod Classic,
the uninstaller DFU this builds is byte-identical to the one built
before this change.

AI provenance: developed with Claude Opus 5 (Anthropic), used through
Claude Code. The model wrote most of the code and this message under
Andrew Rice's direction. Any hardware testing described above was
carried out by Andrew Rice, who is responsible for this change.

Change-Id: I4b2fa692ac4110192ccbde0f1790b0ae2d1c73f5
2026-09-15 16:22:11 -04:00

296 lines
9.2 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2015 by Cástor Muñoz
*
* 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 <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "mks5lboot.h"
/* Win32 compatibility */
#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifdef WIN32
#include <windows.h>
#define sleep_ms(ms) Sleep(ms)
#else
#include <time.h>
static void sleep_ms(unsigned int ms)
{
struct timespec req;
req.tv_sec = ms / 1000;
req.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&req, NULL);
}
#endif
#define DEFAULT_LOOP_PERIOD 1 /* seconds */
#define _ERR(format, ...) \
do { \
snprintf(errstr, errstrsize, "[ERR] "format, __VA_ARGS__); \
goto error; \
} while(0)
static int write_file(char *outfile, unsigned char* buf,
int bufsize, char* errstr, int errstrsize)
{
int fd = open(outfile, O_CREAT|O_TRUNC|O_WRONLY|O_BINARY, 0666);
if (fd < 0)
_ERR("Could not open %s for writing", outfile);
if (write(fd, buf, bufsize) != bufsize)
_ERR("Could not write file %s", outfile);
return 1;
error:
return 0;
}
static unsigned char *read_file(char *infile, int *bufsize,
char* errstr, int errstrsize)
{
unsigned char *buf;
int fd;
struct stat s;
fd = open(infile, O_RDONLY|O_BINARY);
if (fd < 0)
_ERR("Could not open %s for reading", infile);
if (fstat(fd, &s) < 0)
_ERR("Checking size of input file %s", infile);
*bufsize = s.st_size;
buf = malloc(*bufsize);
if (buf == NULL)
_ERR("Could not allocate memory for %s", infile);
if (read(fd, buf, *bufsize) != *bufsize)
_ERR("Could not read file %s", infile);
return buf;
error:
return NULL;
}
static void usage(void)
{
fprintf(stderr,
"Usage:\n"
" mks5lboot --bl-inst <bootloader.ipod> [-p <pid>] [--single]\n"
" --bl-uninst <platform> [-p <pid>]\n"
" --dfuscan [--loop [<sec>]] [-p <pid>]\n"
" --dfusend <infile.dfu> [-p <pid>]\n"
" --dfureset [--loop [<sec>]] [-p <pid>]\n"
" --mkdfu-inst <bootloader.ipod> <outfile.dfu> [--single]\n"
" --mkdfu-uninst <platform> <outfile.dfu>\n"
" --mkdfu-raw <filename.bin> <outfile.dfu>\n"
"\n"
"Commands:\n"
" --bl-inst Install file <bootloader.ipod> into an iPod device\n"
" (same as --mkdfu-inst and --dfusend).\n"
" --bl-uninst Remove a bootloader from an iPod device (same as\n"
" --mkdfu-uninst and --dfusend).\n"
"\n"
" --dfuscan scan for DFU USB devices and outputs the status.\n"
" --dfusend send DFU image <infile.dfu> to the device.\n"
" --dfureset reset DFU USB device bus.\n"
"\n"
" --mkdfu-inst Build a DFU image containing an installer for\n"
" <bootloader.ipod>, save it as <outfile.dfu>.\n"
" --mkdfu-uninst Build a DFU image containing an uninstaler for\n"
" <platform> devices, save it as <outfile.dfu>.\n"
" --mkdfu-raw Build a DFU image containing raw executable\n"
" code, save it ass <outfile.dfu>. <infile.bin>\n"
" is the code you want to run, it is loaded at\n"
" address 0x%08x and executed.\n"
"\n"
" <bootloader.ipod> is the rockbox bootloader that you want to\n"
" install (previously scrambled with tools/scramble utility).\n"
"\n"
" <platform> is the name of the platform (type of device) for\n"
" which the DFU uninstaller will be built. Currently supported\n"
" platform names are:\n"
" ipod6g: iPod Classic 6G\n"
" ipodnano3g: iPod Nano 3G\n"
"\n"
"Options:\n"
" -p, --pid <pid> Use a specific <pid> (Product Id) USB device,\n"
" if this option is ommited then it uses the\n"
" first USB DFU device found.\n"
" -l, --loop <sec> Run the command every <sec> seconds, default\n"
" period (<sec> ommited) is %d seconds.\n"
" -S, --single Be careful using this option. The bootloader\n"
" is installed for single boot, the original\n"
" Apple NOR boot is destroyed (if it exists),\n"
" and only Rockbox can be used.\n"
, DFU_LOADADDR + BIN_OFFSET
, DEFAULT_LOOP_PERIOD);
exit(1);
}
int main(int argc, char* argv[])
{
char *dfuoutfile = NULL;
char *dfuinfile = NULL;
char *dfu_arg = NULL;
int dfu_type = DFU_NONE;
int n_cmds = 0;
int scan = 0;
int pid = 0;
int reset = 0;
int loop = 0;
int single = 0;
char errstr[200];
unsigned char *dfubuf;
int dfusize;
fprintf(stderr,
#if defined(WIN32) && defined(USE_LIBUSBAPI)
"mks5lboot Version " VERSION " (libusb)\n"
#else
"mks5lboot Version " VERSION "\n"
#endif
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"\n");
fflush(stderr);
while (--argc)
{
argv++;
if (!memcmp(*argv, "--bl", 4)) {
if (!strcmp(*argv+4, "-inst")) dfu_type = DFU_INST;
else if (!strcmp(*argv+4, "-uninst")) dfu_type = DFU_UNINST;
else usage();
if (!--argc) usage();
dfu_arg = *++argv;
n_cmds++;
}
else if (!memcmp(*argv, "--mkdfu", 7)) {
if (!strcmp(*argv+7, "-inst")) dfu_type = DFU_INST;
else if (!strcmp(*argv+7, "-uninst")) dfu_type = DFU_UNINST;
else if (!strcmp(*argv+7, "-raw")) dfu_type = DFU_RAW;
else usage();
if (!--argc) usage();
dfu_arg = *++argv;
if (!--argc) usage();
dfuoutfile = *++argv;
n_cmds++;
}
else if (!strcmp(*argv, "--dfusend")) {
if (!--argc) usage();
dfuinfile = *++argv;
n_cmds++;
}
else if (!strcmp(*argv, "--dfuscan")) {
scan = 1;
n_cmds++;
}
else if (!strcmp(*argv, "--dfureset")) {
scan = 1;
reset = 1;
n_cmds++;
}
else if (!strcmp(*argv, "--pid") || !strcmp(*argv, "-p")) {
if (!--argc) usage();
if (sscanf(*++argv, "%x", &pid) != 1) usage();
}
else if (!strcmp(*argv, "--loop") || !strcmp (*argv, "-l")) {
if (!(argc-1) || *(argv+1)[0] == '-') {
loop = DEFAULT_LOOP_PERIOD;
}
else {
if ((sscanf(*++argv, "%d", &loop) != 1) || !loop) usage();
argc--;
}
}
else if (!strcmp(*argv, "--single") || !strcmp(*argv, "-S")) {
single = 1;
}
else if (!strcmp(*argv, "--debug")) {
ipoddfu_debug(1);
}
else
usage();
}
if (n_cmds != 1)
usage();
if ((dfu_type == DFU_INST) && single)
dfu_type = DFU_INST_SINGLE;
if (scan) {
int cnt = 0;
while (1) {
int state, res;
if (loop) printf("[%d] ", cnt);
else printf("[INFO] ");
printf("DFU %s:\n", reset ? "reset":"scan");
res = ipoddfu_scan(pid, &state, reset, errstr, sizeof(errstr));
if (res == 0)
printf("%s\n", errstr);
else
printf("[INFO] DFU device state: %d\n", state);
if (!loop)
exit(!res);
fflush(stdout);
sleep_ms(loop*1000);
cnt += loop;
}
}
if (dfuinfile)
dfubuf = read_file(dfuinfile, &dfusize, errstr, sizeof(errstr));
else
dfubuf = mkdfu(dfu_type, dfu_arg, &dfusize, errstr, sizeof(errstr));
if (!dfubuf)
goto error;
if (dfuoutfile) {
if (write_file(dfuoutfile, dfubuf, dfusize, errstr, sizeof(errstr))) {
printf("[INFO] Created file %s (%d bytes)\n", dfuoutfile, dfusize);
exit(0);
}
}
else {
if (ipoddfu_send(pid, dfubuf, dfusize, errstr, sizeof(errstr))) {
printf("[INFO] DFU image sent successfully (%d bytes)\n", dfusize);
exit(0);
}
}
error:
printf("%s\n", errstr);
exit(1);
}