jztool: add support for Shanling Q1 and Eros Q
Change-Id: I8e93162d1a9d82e9d132219f2803b1856d24ae6c
This commit is contained in:
parent
6f042e91dd
commit
740a50687f
|
@ -15,7 +15,7 @@ CFLAGS += -O0 -ggdb
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LIBSOURCES := src/buffer.c src/context.c src/device_info.c \
|
LIBSOURCES := src/buffer.c src/context.c src/device_info.c \
|
||||||
src/identify_file.c src/fiiom3k.c src/usb.c
|
src/identify_file.c src/ucl_unpack.c src/usb.c src/x1000.c
|
||||||
SOURCES := $(LIBSOURCES) jztool.c
|
SOURCES := $(LIBSOURCES) jztool.c
|
||||||
EXTRADEPS := libucl.a libmicrotar.a
|
EXTRADEPS := libucl.a libmicrotar.a
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,45 @@
|
||||||
# jztool -- Ingenic device utility & bootloader installer
|
# jztool -- Ingenic device utility & bootloader installer
|
||||||
|
|
||||||
The `jztool` utility can help install, backup, and restore the bootloader on
|
The `jztool` utility can help install, backup, and restore the bootloader on
|
||||||
Rockbox players based on a supported Ingenic SoC.
|
Rockbox players based on a supported Ingenic SoC (currently only the X1000).
|
||||||
|
|
||||||
## FiiO M3K
|
## Running jztool
|
||||||
|
|
||||||
First, get a copy of the `bootloader.m3k` file, either by downloading it
|
### Getting a bootloader
|
||||||
from <https://rockbox.org>, or by compiling it yourself (choose 'B'ootloader
|
|
||||||
build when configuring your build).
|
|
||||||
|
|
||||||
The first time you install Rockbox, you need to load the Rockbox bootloader
|
To use `jztool` you need to compile or download a bootloader for your player.
|
||||||
over USB by entering USB boot mode. The easiest way to do this is by plugging
|
It's recommended to use only official released bootloaders, since bootloaders
|
||||||
in the microUSB cable to the M3K and holding the VOL- button while plugging
|
compiled from Git are not tested and might be buggy.
|
||||||
the USB into your computer. If you entered USB boot mode, the button light
|
|
||||||
will turn on but the LCD will remain black.
|
|
||||||
|
|
||||||
Copy the `bootloader.m3k` next to the `jztool` executable and follow the
|
You can download released bootloaders from <https://download.rockbox.org/>.
|
||||||
instructions below which are appropriate to your OS.
|
|
||||||
|
|
||||||
### Running jztool
|
The bootloader file is named after the target: for example, the FiiO M3K
|
||||||
|
bootloader is called `bootloader.m3k`. The FiiO M3K is used as an example
|
||||||
|
here, but the instructions apply to all X1000-based players.
|
||||||
|
|
||||||
#### Linux/Mac
|
Use `jztool --help` to find out the model name of your player.
|
||||||
|
|
||||||
|
### Entering USB boot mode
|
||||||
|
|
||||||
|
USB boot mode is a low-level mode provided by the CPU which allows a computer
|
||||||
|
to load firmware onto the device. You need to put your player into this mode
|
||||||
|
manually before using `jztool` (unfortunately, it can't be done automatically.)
|
||||||
|
|
||||||
|
To connect the player in USB boot mode, follow these steps:
|
||||||
|
|
||||||
|
1. Ensure the player is fully powered off.
|
||||||
|
2. Plug one end of the USB cable into your player.
|
||||||
|
3. Hold down your player's USB boot key (see below).
|
||||||
|
4. Plug the other end of the USB cable into your computer.
|
||||||
|
5. Let go of the USB boot key.
|
||||||
|
|
||||||
|
The USB boot key depends on your player:
|
||||||
|
|
||||||
|
- FiiO M3K: Volume Down
|
||||||
|
- Shanling Q1: Play
|
||||||
|
- Eros Q: Menu
|
||||||
|
|
||||||
|
### Linux/Mac
|
||||||
|
|
||||||
Run the following command in a terminal. Note that on Linux, you will need to
|
Run the following command in a terminal. Note that on Linux, you will need to
|
||||||
have root access to allow libusb to access the USB device.
|
have root access to allow libusb to access the USB device.
|
||||||
|
@ -32,9 +51,9 @@ have root access to allow libusb to access the USB device.
|
||||||
$ ./jztool fiiom3k load bootloader.m3k
|
$ ./jztool fiiom3k load bootloader.m3k
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Windows
|
### Windows
|
||||||
|
|
||||||
To allow `jztool` access to the M3K in USB boot mode, you need to install
|
To allow `jztool` access to your player in USB boot mode, you need to install
|
||||||
the WinUSB driver. The recommended way to install it is using Zadig, which
|
the WinUSB driver. The recommended way to install it is using Zadig, which
|
||||||
may be downloaded from its homepage <https://zadig.akeo.ie>. Please note
|
may be downloaded from its homepage <https://zadig.akeo.ie>. Please note
|
||||||
this is 3rd party software not maintained or supported by Rockbox developers.
|
this is 3rd party software not maintained or supported by Rockbox developers.
|
||||||
|
@ -42,10 +61,10 @@ this is 3rd party software not maintained or supported by Rockbox developers.
|
||||||
|
|
||||||
When running Zadig you must select the WinUSB driver; the other driver options
|
When running Zadig you must select the WinUSB driver; the other driver options
|
||||||
will not work properly with `jztool`. You will have to select the correct USB
|
will not work properly with `jztool`. You will have to select the correct USB
|
||||||
device in Zadig -- the name and USB IDs of the M3K in USB boot mode are listed
|
device in Zadig. All X1000-based players use the same USB ID while in USB boot
|
||||||
below. NOTE: the device name may show only as "X" and a hollow square in Zadig.
|
mode, listed below. NOTE: the device name may show only as "X" and a hollow
|
||||||
The IDs will not change, so those are the most reliable way to confirm you have
|
square in Zadig. The IDs will not change, so those are the most reliable way
|
||||||
selected the correct device.
|
to confirm you have selected the correct device.
|
||||||
|
|
||||||
```
|
```
|
||||||
Name: Ingenic Semiconductor Co.,Ltd X1000
|
Name: Ingenic Semiconductor Co.,Ltd X1000
|
||||||
|
@ -63,21 +82,27 @@ Type the following command to load the Rockbox bootloader:
|
||||||
$ jztool.exe fiiom3k load bootloader.m3k
|
$ jztool.exe fiiom3k load bootloader.m3k
|
||||||
```
|
```
|
||||||
|
|
||||||
### Further instructions
|
## Using the recovery menu
|
||||||
|
|
||||||
After running `jztool` successfully your M3K will display the recovery menu
|
If `jztool` runs successfully your player will display the Rockbox bootloader's
|
||||||
of the Rockbox bootloader. If you want to permanently install Rockbox to your
|
recovery menu. If you want to permanently install Rockbox to your device, copy
|
||||||
M3K, copy `bootloader.m3k` to the root of an SD card, insert it to your device,
|
the bootloader file you downloaded to the root of your SD card, insert the SD
|
||||||
then choose "Install/update bootloader" from the menu.
|
card to your player, and choose "Install/update bootloader" from the menu.
|
||||||
|
|
||||||
It is _highly_ recommended that you take a backup of your existing bootloader
|
It is _highly_ recommended that you take a backup of your existing bootloader
|
||||||
in case of any trouble -- choose "Backup bootloader" from the recovery menu.
|
in case of any trouble -- choose "Backup bootloader" from the recovery menu.
|
||||||
The backup file is called "fiiom3k-boot.bin" and will be saved to the root of
|
The backup file is called `PLAYER-boot.bin`, where `PLAYER` is the model name.
|
||||||
the SD card. If you need to restore it, simply place the file at the root of
|
(Example: `fiiom3k-boot.bin`.)
|
||||||
your SD card and select "Restore bootloader".
|
|
||||||
|
|
||||||
In the future if you want to backup, restore, or update the bootloader, you
|
You can restore the backup later by putting it on the root of your SD card and
|
||||||
can access the Rockbox bootloader's recovery menu by holding VOL+ when booting.
|
selecting "Restor bootloader" in the recovery menu.
|
||||||
|
|
||||||
|
After installing the Rockbox bootloader, you can access the recovery menu by
|
||||||
|
holding a key while booting:
|
||||||
|
|
||||||
|
- FiiO M3K: Volume Up
|
||||||
|
- Shanling Q1: Next (button on the lower left)
|
||||||
|
- Eros Q: Volume Up
|
||||||
|
|
||||||
### Known issues
|
### Known issues
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,12 @@ extern "C" {
|
||||||
* Types, enumerations, etc
|
* Types, enumerations, etc
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define JZ_CPUINFO_BUFLEN 9
|
||||||
|
|
||||||
typedef struct jz_context jz_context;
|
typedef struct jz_context jz_context;
|
||||||
typedef struct jz_usbdev jz_usbdev;
|
typedef struct jz_usbdev jz_usbdev;
|
||||||
typedef struct jz_device_info jz_device_info;
|
typedef struct jz_device_info jz_device_info;
|
||||||
|
typedef struct jz_cpu_info jz_cpu_info;
|
||||||
typedef struct jz_buffer jz_buffer;
|
typedef struct jz_buffer jz_buffer;
|
||||||
|
|
||||||
typedef enum jz_error jz_error;
|
typedef enum jz_error jz_error;
|
||||||
|
@ -77,21 +80,48 @@ enum jz_log_level {
|
||||||
|
|
||||||
enum jz_device_type {
|
enum jz_device_type {
|
||||||
JZ_DEVICE_FIIOM3K,
|
JZ_DEVICE_FIIOM3K,
|
||||||
|
JZ_DEVICE_SHANLINGQ1,
|
||||||
|
JZ_DEVICE_EROSQ,
|
||||||
|
JZ_NUM_DEVICES,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum jz_cpu_type {
|
enum jz_cpu_type {
|
||||||
JZ_CPU_X1000,
|
JZ_CPU_X1000,
|
||||||
|
JZ_NUM_CPUS,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct jz_device_info {
|
struct jz_device_info {
|
||||||
|
/* internal device name and file extension */
|
||||||
const char* name;
|
const char* name;
|
||||||
|
const char* file_ext;
|
||||||
|
|
||||||
|
/* human-readable name */
|
||||||
const char* description;
|
const char* description;
|
||||||
|
|
||||||
|
/* device and CPU type */
|
||||||
jz_device_type device_type;
|
jz_device_type device_type;
|
||||||
jz_cpu_type cpu_type;
|
jz_cpu_type cpu_type;
|
||||||
|
|
||||||
|
/* USB IDs of the device in mass storage mode */
|
||||||
uint16_t vendor_id;
|
uint16_t vendor_id;
|
||||||
uint16_t product_id;
|
uint16_t product_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct jz_cpu_info {
|
||||||
|
/* CPU info string, as reported by the boot ROM */
|
||||||
|
const char* info_str;
|
||||||
|
|
||||||
|
/* USB IDs of the boot ROM */
|
||||||
|
uint16_t vendor_id;
|
||||||
|
uint16_t product_id;
|
||||||
|
|
||||||
|
/* default addresses for running binaries */
|
||||||
|
uint32_t stage1_load_addr;
|
||||||
|
uint32_t stage1_exec_addr;
|
||||||
|
uint32_t stage2_load_addr;
|
||||||
|
uint32_t stage2_exec_addr;
|
||||||
|
};
|
||||||
|
|
||||||
struct jz_buffer {
|
struct jz_buffer {
|
||||||
size_t size;
|
size_t size;
|
||||||
uint8_t* data;
|
uint8_t* data;
|
||||||
|
@ -119,11 +149,13 @@ void jz_sleepms(int ms);
|
||||||
* Device and file info
|
* Device and file info
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int jz_get_num_device_info(void);
|
|
||||||
const jz_device_info* jz_get_device_info(jz_device_type type);
|
const jz_device_info* jz_get_device_info(jz_device_type type);
|
||||||
const jz_device_info* jz_get_device_info_named(const char* name);
|
const jz_device_info* jz_get_device_info_named(const char* name);
|
||||||
const jz_device_info* jz_get_device_info_indexed(int index);
|
const jz_device_info* jz_get_device_info_indexed(int index);
|
||||||
|
|
||||||
|
const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type);
|
||||||
|
const jz_cpu_info* jz_get_cpu_info_named(const char* info_str);
|
||||||
|
|
||||||
int jz_identify_x1000_spl(const void* data, size_t len);
|
int jz_identify_x1000_spl(const void* data, size_t len);
|
||||||
int jz_identify_scramble_image(const void* data, size_t len);
|
int jz_identify_scramble_image(const void* data, size_t len);
|
||||||
|
|
||||||
|
@ -139,15 +171,16 @@ int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data);
|
||||||
int jz_usb_start1(jz_usbdev* dev, uint32_t addr);
|
int jz_usb_start1(jz_usbdev* dev, uint32_t addr);
|
||||||
int jz_usb_start2(jz_usbdev* dev, uint32_t addr);
|
int jz_usb_start2(jz_usbdev* dev, uint32_t addr);
|
||||||
int jz_usb_flush_caches(jz_usbdev* dev);
|
int jz_usb_flush_caches(jz_usbdev* dev);
|
||||||
|
int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen);
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Rockbox loader (all functions are model-specific, see docs)
|
* Rockbox loader (all functions are model-specific, see docs)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename);
|
int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename);
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Simple buffer API
|
* Buffer API and other functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
jz_buffer* jz_buffer_alloc(size_t size, const void* data);
|
jz_buffer* jz_buffer_alloc(size_t size, const void* data);
|
||||||
|
@ -156,6 +189,8 @@ void jz_buffer_free(jz_buffer* buf);
|
||||||
int jz_buffer_load(jz_buffer** buf, const char* filename);
|
int jz_buffer_load(jz_buffer** buf, const char* filename);
|
||||||
int jz_buffer_save(jz_buffer* buf, const char* filename);
|
int jz_buffer_save(jz_buffer* buf, const char* filename);
|
||||||
|
|
||||||
|
jz_buffer* jz_ucl_unpack(const uint8_t* src, uint32_t src_len, uint32_t* dst_len);
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* END
|
* END
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,49 +28,63 @@
|
||||||
jz_context* jz = NULL;
|
jz_context* jz = NULL;
|
||||||
jz_usbdev* usbdev = NULL;
|
jz_usbdev* usbdev = NULL;
|
||||||
const jz_device_info* dev_info = NULL;
|
const jz_device_info* dev_info = NULL;
|
||||||
|
const jz_cpu_info* cpu_info = NULL;
|
||||||
|
|
||||||
void usage_fiiom3k(void)
|
void usage_x1000(void)
|
||||||
{
|
{
|
||||||
printf("Usage:\n"
|
printf(
|
||||||
" jztool fiiom3k load <bootloader.m3k>\n"
|
"Usage:\n"
|
||||||
"\n"
|
" jztool fiiom3k load <bootloader.m3k>\n"
|
||||||
"The 'load' command is used to boot the Rockbox bootloader in\n"
|
" jztool shanlingq1 load <bootloader.q1>\n"
|
||||||
"recovery mode, which allows you to install the Rockbox bootloader\n"
|
" jztool erosq load <bootloader.erosq>\n"
|
||||||
"and backup or restore bootloader images. You need to connect the\n"
|
"\n"
|
||||||
"M3K in USB boot mode in order to use this tool.\n"
|
"The 'load' command is used to boot the Rockbox bootloader in recovery\n"
|
||||||
"\n"
|
"mode, which allows you to install the Rockbox bootloader and backup or\n"
|
||||||
"On Windows, you will need to install the WinUSB driver for the M3K\n"
|
"restore bootloader images. You need to connect your player in USB boot\n"
|
||||||
"using a 3rd-party tool such as Zadig <https://zadig.akeo.ie>. For\n"
|
"mode in order to use this tool.\n"
|
||||||
"more details check the jztool README.md file or the Rockbox wiki at\n"
|
"\n"
|
||||||
"<https://rockbox.org/wiki/FiioM3K>.\n"
|
"To connect the player in USB boot mode, follow these steps:\n"
|
||||||
"\n"
|
"\n"
|
||||||
"To connect the M3K in USB boot mode, plug the microUSB into the\n"
|
"1. Ensure the player is fully powered off.\n"
|
||||||
"M3K, and hold the VOL- button while plugging the USB into your\n"
|
"2. Plug one end of the USB cable into your player.\n"
|
||||||
"computer. If successful, the button light will turn on and the\n"
|
"3. Hold down your player's USB boot key (see below).\n"
|
||||||
"LCD will remain black. If you encounter any errors and need to\n"
|
"4. Plug the other end of the USB cable into your computer.\n"
|
||||||
"reconnect the device, you must force a power off by holding POWER\n"
|
"5. Let go of the USB boot key.\n"
|
||||||
"for more than 10 seconds.\n"
|
"\n"
|
||||||
"\n"
|
"USB boot keys:\n"
|
||||||
"Once the Rockbox bootloader is installed on your M3K, you can\n"
|
"\n"
|
||||||
"access the recovery menu by holding VOL+ while powering on the\n"
|
" FiiO M3K - Volume Down\n"
|
||||||
"device.\n");
|
" Shanling Q1 - Play\n"
|
||||||
|
" Eros Q - Menu\n"
|
||||||
|
"\n"
|
||||||
|
"Not all players give a visible indication that they are in USB boot mode.\n"
|
||||||
|
"If you're having trouble connecting your player, try resetting it by\n"
|
||||||
|
"holding the power button for 10 seconds, and try the above steps again.\n"
|
||||||
|
"\n"
|
||||||
|
"Note for Windows users: you need to install the WinUSB driver using a\n"
|
||||||
|
"3rd-party tool such as Zadig <https://zadig.akeo.ie> before this tool\n"
|
||||||
|
"can access your player in USB boot mode. You need to run Zadig while the\n"
|
||||||
|
"player is plugged in and in USB boot mode. For more details check the\n"
|
||||||
|
"jztool README.md file or visit <https://rockbox.org/wiki/IngenicX1000>.\n"
|
||||||
|
"\n");
|
||||||
|
|
||||||
exit(4);
|
exit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmdline_fiiom3k(int argc, char** argv)
|
int cmdline_x1000(int argc, char** argv)
|
||||||
{
|
{
|
||||||
if(argc < 2 || strcmp(argv[0], "load")) {
|
if(argc < 2 || strcmp(argv[0], "load")) {
|
||||||
usage_fiiom3k();
|
usage_x1000();
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rc = jz_usb_open(jz, &usbdev, dev_info->vendor_id, dev_info->product_id);
|
int rc = jz_usb_open(jz, &usbdev, cpu_info->vendor_id, cpu_info->product_id);
|
||||||
if(rc < 0) {
|
if(rc < 0) {
|
||||||
jz_log(jz, JZ_LOG_ERROR, "Cannot open USB device: %d", rc);
|
jz_log(jz, JZ_LOG_ERROR, "Cannot open USB device: %d", rc);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = jz_fiiom3k_boot(usbdev, argv[1]);
|
rc = jz_x1000_boot(usbdev, dev_info->device_type, argv[1]);
|
||||||
if(rc < 0) {
|
if(rc < 0) {
|
||||||
jz_log(jz, JZ_LOG_ERROR, "Boot failed: %d", rc);
|
jz_log(jz, JZ_LOG_ERROR, "Boot failed: %d", rc);
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -90,8 +104,7 @@ void usage(void)
|
||||||
" -v, --verbose Display detailed logging output\n\n");
|
" -v, --verbose Display detailed logging output\n\n");
|
||||||
|
|
||||||
printf("Supported devices:\n\n");
|
printf("Supported devices:\n\n");
|
||||||
int n = jz_get_num_device_info();
|
for(int i = 0; i < JZ_NUM_DEVICES; ++i) {
|
||||||
for(int i = 0; i < n; ++i) {
|
|
||||||
const jz_device_info* info = jz_get_device_info_indexed(i);
|
const jz_device_info* info = jz_get_device_info_indexed(i);
|
||||||
printf(" %s - %s\n", info->name, info->description);
|
printf(" %s - %s\n", info->name, info->description);
|
||||||
}
|
}
|
||||||
|
@ -182,11 +195,15 @@ int main(int argc, char** argv)
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cpu_info = jz_get_cpu_info(dev_info->cpu_type);
|
||||||
|
|
||||||
/* Dispatch to device handler */
|
/* Dispatch to device handler */
|
||||||
--argc, ++argv;
|
--argc, ++argv;
|
||||||
switch(dev_info->device_type) {
|
switch(dev_info->device_type) {
|
||||||
case JZ_DEVICE_FIIOM3K:
|
case JZ_DEVICE_FIIOM3K:
|
||||||
return cmdline_fiiom3k(argc, argv);
|
case JZ_DEVICE_SHANLINGQ1:
|
||||||
|
case JZ_DEVICE_EROSQ:
|
||||||
|
return cmdline_x1000(argc, argv);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
jz_log(jz, JZ_LOG_ERROR, "INTERNAL ERROR: unhandled device type");
|
jz_log(jz, JZ_LOG_ERROR, "INTERNAL ERROR: unhandled device type");
|
||||||
|
|
|
@ -22,39 +22,58 @@
|
||||||
#include "jztool.h"
|
#include "jztool.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static const jz_device_info infotable[] = {
|
static const jz_device_info infotable[JZ_NUM_DEVICES] = {
|
||||||
{
|
[JZ_DEVICE_FIIOM3K] = {
|
||||||
.name = "fiiom3k",
|
.name = "fiiom3k",
|
||||||
|
.file_ext = "m3k",
|
||||||
.description = "FiiO M3K",
|
.description = "FiiO M3K",
|
||||||
.device_type = JZ_DEVICE_FIIOM3K,
|
.device_type = JZ_DEVICE_FIIOM3K,
|
||||||
.cpu_type = JZ_CPU_X1000,
|
.cpu_type = JZ_CPU_X1000,
|
||||||
.vendor_id = 0xa108,
|
.vendor_id = 0x2972,
|
||||||
.product_id = 0x1000,
|
.product_id = 0x0003,
|
||||||
|
},
|
||||||
|
[JZ_DEVICE_SHANLINGQ1] = {
|
||||||
|
.name = "shanlingq1",
|
||||||
|
.file_ext = "q1",
|
||||||
|
.description = "Shanling Q1",
|
||||||
|
.device_type = JZ_DEVICE_SHANLINGQ1,
|
||||||
|
.cpu_type = JZ_CPU_X1000,
|
||||||
|
.vendor_id = 0x0525,
|
||||||
|
.product_id = 0xa4a5,
|
||||||
|
},
|
||||||
|
[JZ_DEVICE_EROSQ] = {
|
||||||
|
.name = "erosq",
|
||||||
|
.file_ext = "erosq",
|
||||||
|
.description = "AIGO Eros Q",
|
||||||
|
.device_type = JZ_DEVICE_EROSQ,
|
||||||
|
.cpu_type = JZ_CPU_X1000,
|
||||||
|
.vendor_id = 0xc502,
|
||||||
|
.product_id = 0x0023,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int infotable_size = sizeof(infotable)/sizeof(struct jz_device_info);
|
static const jz_cpu_info cputable[JZ_NUM_CPUS] = {
|
||||||
|
[JZ_CPU_X1000] = {
|
||||||
/** \brief Get the number of entries in the device info list */
|
.info_str = "X1000_v1",
|
||||||
int jz_get_num_device_info(void)
|
.vendor_id = 0xa108,
|
||||||
{
|
.product_id = 0x1000,
|
||||||
return infotable_size;
|
.stage1_load_addr = 0xf4001000,
|
||||||
}
|
.stage1_exec_addr = 0xf4001800,
|
||||||
|
.stage2_load_addr = 0x80004000,
|
||||||
|
.stage2_exec_addr = 0x80004000,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/** \brief Lookup info for a device by type, returns NULL if not found. */
|
/** \brief Lookup info for a device by type, returns NULL if not found. */
|
||||||
const jz_device_info* jz_get_device_info(jz_device_type type)
|
const jz_device_info* jz_get_device_info(jz_device_type type)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < infotable_size; ++i)
|
return jz_get_device_info_indexed(type);
|
||||||
if(infotable[i].device_type == type)
|
|
||||||
return &infotable[i];
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \brief Lookup info for a device by name, returns NULL if not found. */
|
/** \brief Lookup info for a device by name, returns NULL if not found. */
|
||||||
const jz_device_info* jz_get_device_info_named(const char* name)
|
const jz_device_info* jz_get_device_info_named(const char* name)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < infotable_size; ++i)
|
for(int i = 0; i < JZ_NUM_DEVICES; ++i)
|
||||||
if(!strcmp(infotable[i].name, name))
|
if(!strcmp(infotable[i].name, name))
|
||||||
return &infotable[i];
|
return &infotable[i];
|
||||||
|
|
||||||
|
@ -64,8 +83,27 @@ const jz_device_info* jz_get_device_info_named(const char* name)
|
||||||
/** \brief Get a device info entry by index, returns NULL if out of range. */
|
/** \brief Get a device info entry by index, returns NULL if out of range. */
|
||||||
const jz_device_info* jz_get_device_info_indexed(int index)
|
const jz_device_info* jz_get_device_info_indexed(int index)
|
||||||
{
|
{
|
||||||
if(index < infotable_size)
|
if(index < JZ_NUM_DEVICES)
|
||||||
return &infotable[index];
|
return &infotable[index];
|
||||||
else
|
else
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** \brief Lookup info for a CPU, returns NULL if not found. */
|
||||||
|
const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type)
|
||||||
|
{
|
||||||
|
if(type < JZ_NUM_CPUS)
|
||||||
|
return &cputable[type];
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief Lookup info for a CPU by info string, returns NULL if not found. */
|
||||||
|
const jz_cpu_info* jz_get_cpu_info_named(const char* info_str)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < JZ_NUM_CPUS; ++i)
|
||||||
|
if(!strcmp(cputable[i].info_str, info_str))
|
||||||
|
return &cputable[i];
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
|
@ -118,43 +118,52 @@ int jz_identify_x1000_spl(const void* data, size_t len)
|
||||||
static const struct scramble_model_info {
|
static const struct scramble_model_info {
|
||||||
const char* name;
|
const char* name;
|
||||||
int model_num;
|
int model_num;
|
||||||
|
size_t offset_crc;
|
||||||
|
size_t offset_name;
|
||||||
|
size_t offset_data;
|
||||||
} scramble_models[] = {
|
} scramble_models[] = {
|
||||||
{"fiio", 114},
|
{"fiio", 114, 0, 4, 8},
|
||||||
{NULL, 0},
|
{"shq1", 115, 0, 4, 8},
|
||||||
|
{"eros", 116, 0, 4, 8},
|
||||||
|
{NULL, 0, 0, 0, 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** \brief Identify a file as a Rockbox `scramble` image
|
/** \brief Identify a file as a Rockbox `scramble` image
|
||||||
* \param data File data buffer
|
* \param data File data buffer
|
||||||
* \param len Length of file
|
* \param len Length of file
|
||||||
* \return JZ_SUCCESS if file looks correct, or one of the following errors
|
* \return JZ_SUCCESS if file looks correct, or one of the following errors
|
||||||
* \retval JZ_IDERR_WRONG_SIZE file too small to be valid
|
|
||||||
* \retval JZ_IDERR_UNRECOGNIZED_MODEL unsupported/unknown model type
|
* \retval JZ_IDERR_UNRECOGNIZED_MODEL unsupported/unknown model type
|
||||||
* \retval JZ_IDERR_BAD_CHECKSUM checksum mismatch
|
* \retval JZ_IDERR_BAD_CHECKSUM checksum mismatch
|
||||||
*/
|
*/
|
||||||
int jz_identify_scramble_image(const void* data, size_t len)
|
int jz_identify_scramble_image(const void* data, size_t len)
|
||||||
{
|
{
|
||||||
/* 4 bytes checksum + 4 bytes player model */
|
const uint8_t* dat;
|
||||||
if(len < 8)
|
const struct scramble_model_info* model_info;
|
||||||
return JZ_IDERR_WRONG_SIZE;
|
uint32_t sum, file_sum;
|
||||||
|
|
||||||
|
dat = (const uint8_t*)data;
|
||||||
|
model_info = &scramble_models[0];
|
||||||
|
|
||||||
/* Look up the model number */
|
/* Look up the model number */
|
||||||
const uint8_t* dat = (const uint8_t*)data;
|
for(; model_info->name != NULL; ++model_info) {
|
||||||
const struct scramble_model_info* model_info = &scramble_models[0];
|
if(model_info->offset_name + 4 > len)
|
||||||
for(; model_info->name != NULL; ++model_info)
|
continue;
|
||||||
if(!memcmp(&dat[4], model_info->name, 4))
|
if(!memcmp(&dat[model_info->offset_name], model_info->name, 4))
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if(model_info->name == NULL)
|
if(model_info->name == NULL)
|
||||||
return JZ_IDERR_UNRECOGNIZED_MODEL;
|
return JZ_IDERR_UNRECOGNIZED_MODEL;
|
||||||
|
|
||||||
/* Compute the checksum */
|
/* Compute the checksum */
|
||||||
uint32_t sum = model_info->model_num;
|
sum = model_info->model_num;
|
||||||
for(size_t i = 8; i < len; ++i)
|
for(size_t i = model_info->offset_data; i < len; ++i)
|
||||||
sum += dat[i];
|
sum += dat[i];
|
||||||
|
|
||||||
/* Compare with file's checksum, it's stored in big-endian form */
|
/* Compare with file's checksum, it's stored in big-endian form */
|
||||||
uint32_t fsum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3];
|
dat += model_info->offset_crc;
|
||||||
if(sum != fsum)
|
file_sum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3];
|
||||||
|
if(sum != file_sum)
|
||||||
return JZ_IDERR_BAD_CHECKSUM;
|
return JZ_IDERR_BAD_CHECKSUM;
|
||||||
|
|
||||||
return JZ_SUCCESS;
|
return JZ_SUCCESS;
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 Aidan MacDonald
|
||||||
|
*
|
||||||
|
* 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 "jztool.h"
|
||||||
|
#include "ucl/ucl.h"
|
||||||
|
|
||||||
|
static uint32_t xread32(const uint8_t* d)
|
||||||
|
{
|
||||||
|
uint32_t r = 0;
|
||||||
|
r |= d[0] << 24;
|
||||||
|
r |= d[1] << 16;
|
||||||
|
r |= d[2] << 8;
|
||||||
|
r |= d[3] << 0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* adapted from firmware/common/ucl_decompress.c */
|
||||||
|
jz_buffer* jz_ucl_unpack(const uint8_t* src, uint32_t src_len, uint32_t* dst_len)
|
||||||
|
{
|
||||||
|
static const uint8_t magic[8] =
|
||||||
|
{0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a};
|
||||||
|
|
||||||
|
jz_buffer* buffer = NULL;
|
||||||
|
|
||||||
|
/* make sure there are enough bytes for the header */
|
||||||
|
if(src_len < 18)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
/* avoid memcmp for reasons of code size */
|
||||||
|
for(size_t i = 0; i < sizeof(magic); ++i)
|
||||||
|
if(src[i] != magic[i])
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
/* read the other header fields */
|
||||||
|
/* uint32_t flags = xread32(&src[8]); */
|
||||||
|
uint8_t method = src[12];
|
||||||
|
/* uint8_t level = src[13]; */
|
||||||
|
uint32_t block_size = xread32(&src[14]);
|
||||||
|
|
||||||
|
/* check supported compression method */
|
||||||
|
if(method != 0x2e)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
/* validate */
|
||||||
|
if(block_size < 1024 || block_size > 8*1024*1024)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
src += 18;
|
||||||
|
src_len -= 18;
|
||||||
|
|
||||||
|
/* Calculate amount of space that we might need & allocate a buffer:
|
||||||
|
* - subtract 4 to account for end of file marker
|
||||||
|
* - each block is block_size bytes + 8 bytes of header
|
||||||
|
* - add one to nr_blocks to account for case where file size < block size
|
||||||
|
* - total size = max uncompressed size of block * nr_blocks
|
||||||
|
*/
|
||||||
|
uint32_t nr_blocks = (src_len - 4) / (8 + block_size) + 1;
|
||||||
|
uint32_t max_size = nr_blocks * (block_size + block_size/8 + 256);
|
||||||
|
buffer = jz_buffer_alloc(max_size, NULL);
|
||||||
|
if(!buffer)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
/* perform the decompression */
|
||||||
|
uint32_t dst_ilen = buffer->size;
|
||||||
|
uint8_t* dst = buffer->data;
|
||||||
|
while(1) {
|
||||||
|
if(src_len < 4)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
uint32_t out_len = xread32(src); src += 4, src_len -= 4;
|
||||||
|
if(out_len == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(src_len < 4)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
uint32_t in_len = xread32(src); src += 4, src_len -= 4;
|
||||||
|
if(in_len > block_size || out_len > block_size ||
|
||||||
|
in_len == 0 || in_len > out_len)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if(src_len < in_len)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if(in_len < out_len) {
|
||||||
|
uint32_t actual_out_len = dst_ilen;
|
||||||
|
int rc = ucl_nrv2e_decompress_safe_8(src, in_len, dst, &actual_out_len, NULL);
|
||||||
|
if(rc != UCL_E_OK)
|
||||||
|
goto error;
|
||||||
|
if(actual_out_len != out_len)
|
||||||
|
goto error;
|
||||||
|
} else {
|
||||||
|
for(size_t i = 0; i < in_len; ++i)
|
||||||
|
dst[i] = src[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
src += in_len;
|
||||||
|
src_len -= in_len;
|
||||||
|
dst += out_len;
|
||||||
|
dst_ilen -= out_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* subtract leftover number of bytes to get size of compressed output */
|
||||||
|
*dst_len = buffer->size - dst_ilen;
|
||||||
|
return buffer;
|
||||||
|
|
||||||
|
error:
|
||||||
|
jz_buffer_free(buffer);
|
||||||
|
return NULL;
|
||||||
|
}
|
|
@ -22,6 +22,7 @@
|
||||||
#include "jztool_private.h"
|
#include "jztool_private.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#define VR_GET_CPU_INFO 0
|
#define VR_GET_CPU_INFO 0
|
||||||
#define VR_SET_DATA_ADDRESS 1
|
#define VR_SET_DATA_ADDRESS 1
|
||||||
|
@ -145,11 +146,12 @@ void jz_usb_close(jz_usbdev* dev)
|
||||||
|
|
||||||
// Does an Ingenic-specific vendor request
|
// Does an Ingenic-specific vendor request
|
||||||
// Written with X1000 in mind but other Ingenic CPUs have the same commands
|
// Written with X1000 in mind but other Ingenic CPUs have the same commands
|
||||||
static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg)
|
static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg,
|
||||||
|
void* buffer, int buflen)
|
||||||
{
|
{
|
||||||
int rc = libusb_control_transfer(dev->handle,
|
int rc = libusb_control_transfer(dev->handle,
|
||||||
LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
|
LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
|
||||||
req, arg >> 16, arg & 0xffff, NULL, 0, 1000);
|
req, arg >> 16, arg & 0xffff, buffer, buflen, 1000);
|
||||||
|
|
||||||
if(rc < 0) {
|
if(rc < 0) {
|
||||||
jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc));
|
jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc));
|
||||||
|
@ -200,11 +202,11 @@ static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr,
|
||||||
size_t len, void* data)
|
size_t len, void* data)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr);
|
rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr, NULL, 0);
|
||||||
if(rc < 0)
|
if(rc < 0)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len);
|
rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len, NULL, 0);
|
||||||
if(rc < 0)
|
if(rc < 0)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
|
@ -242,7 +244,7 @@ int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data)
|
||||||
*/
|
*/
|
||||||
int jz_usb_start1(jz_usbdev* dev, uint32_t addr)
|
int jz_usb_start1(jz_usbdev* dev, uint32_t addr)
|
||||||
{
|
{
|
||||||
return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr);
|
return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr, NULL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \brief Execute stage2 program jumping to the specified address
|
/** \brief Execute stage2 program jumping to the specified address
|
||||||
|
@ -252,7 +254,7 @@ int jz_usb_start1(jz_usbdev* dev, uint32_t addr)
|
||||||
*/
|
*/
|
||||||
int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
|
int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
|
||||||
{
|
{
|
||||||
return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr);
|
return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr, NULL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \brief Ask device to flush CPU caches
|
/** \brief Ask device to flush CPU caches
|
||||||
|
@ -261,5 +263,29 @@ int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
|
||||||
*/
|
*/
|
||||||
int jz_usb_flush_caches(jz_usbdev* dev)
|
int jz_usb_flush_caches(jz_usbdev* dev)
|
||||||
{
|
{
|
||||||
return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0);
|
return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief Ask device for CPU info string
|
||||||
|
* \param dev USB device
|
||||||
|
* \param buffer Buffer to hold the info string
|
||||||
|
* \param buflen Size of the buffer, in bytes
|
||||||
|
* \return either JZ_SUCCESS on success or a failure code
|
||||||
|
*
|
||||||
|
* The buffer will always be null terminated, but to ensure the info string is
|
||||||
|
* not truncated the buffer needs to be at least `JZ_CPUINFO_BUFLEN` byes long.
|
||||||
|
*/
|
||||||
|
int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen)
|
||||||
|
{
|
||||||
|
char tmpbuf[JZ_CPUINFO_BUFLEN];
|
||||||
|
int rc = jz_usb_vendor_req(dev, VR_GET_CPU_INFO, 0, tmpbuf, 8);
|
||||||
|
if(rc != JZ_SUCCESS)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
if(buflen > 0) {
|
||||||
|
strncpy(buffer, tmpbuf, buflen);
|
||||||
|
buffer[buflen - 1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JZ_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,117 +22,11 @@
|
||||||
#include "jztool.h"
|
#include "jztool.h"
|
||||||
#include "jztool_private.h"
|
#include "jztool_private.h"
|
||||||
#include "microtar.h"
|
#include "microtar.h"
|
||||||
#include "ucl/ucl.h"
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static uint32_t xread32(const uint8_t* d)
|
/* TODO: these functions could be refactored to be CPU-agnostic */
|
||||||
{
|
static int run_stage1(jz_usbdev* dev, jz_buffer* buf)
|
||||||
uint32_t r = 0;
|
|
||||||
r |= d[0] << 24;
|
|
||||||
r |= d[1] << 16;
|
|
||||||
r |= d[2] << 8;
|
|
||||||
r |= d[3] << 0;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* adapted from firmware/common/ucl_decompress.c */
|
|
||||||
static jz_buffer* ucl_unpack(const uint8_t* src, uint32_t src_len,
|
|
||||||
uint32_t* dst_len)
|
|
||||||
{
|
|
||||||
static const uint8_t magic[8] =
|
|
||||||
{0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a};
|
|
||||||
|
|
||||||
jz_buffer* buffer = NULL;
|
|
||||||
|
|
||||||
/* make sure there are enough bytes for the header */
|
|
||||||
if(src_len < 18)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/* avoid memcmp for reasons of code size */
|
|
||||||
for(size_t i = 0; i < sizeof(magic); ++i)
|
|
||||||
if(src[i] != magic[i])
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/* read the other header fields */
|
|
||||||
/* uint32_t flags = xread32(&src[8]); */
|
|
||||||
uint8_t method = src[12];
|
|
||||||
/* uint8_t level = src[13]; */
|
|
||||||
uint32_t block_size = xread32(&src[14]);
|
|
||||||
|
|
||||||
/* check supported compression method */
|
|
||||||
if(method != 0x2e)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/* validate */
|
|
||||||
if(block_size < 1024 || block_size > 8*1024*1024)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
src += 18;
|
|
||||||
src_len -= 18;
|
|
||||||
|
|
||||||
/* Calculate amount of space that we might need & allocate a buffer:
|
|
||||||
* - subtract 4 to account for end of file marker
|
|
||||||
* - each block is block_size bytes + 8 bytes of header
|
|
||||||
* - add one to nr_blocks to account for case where file size < block size
|
|
||||||
* - total size = max uncompressed size of block * nr_blocks
|
|
||||||
*/
|
|
||||||
uint32_t nr_blocks = (src_len - 4) / (8 + block_size) + 1;
|
|
||||||
uint32_t max_size = nr_blocks * (block_size + block_size/8 + 256);
|
|
||||||
buffer = jz_buffer_alloc(max_size, NULL);
|
|
||||||
if(!buffer)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/* perform the decompression */
|
|
||||||
uint32_t dst_ilen = buffer->size;
|
|
||||||
uint8_t* dst = buffer->data;
|
|
||||||
while(1) {
|
|
||||||
if(src_len < 4)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
uint32_t out_len = xread32(src); src += 4, src_len -= 4;
|
|
||||||
if(out_len == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if(src_len < 4)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
uint32_t in_len = xread32(src); src += 4, src_len -= 4;
|
|
||||||
if(in_len > block_size || out_len > block_size ||
|
|
||||||
in_len == 0 || in_len > out_len)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if(src_len < in_len)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if(in_len < out_len) {
|
|
||||||
uint32_t actual_out_len = dst_ilen;
|
|
||||||
int rc = ucl_nrv2e_decompress_safe_8(src, in_len, dst, &actual_out_len, NULL);
|
|
||||||
if(rc != UCL_E_OK)
|
|
||||||
goto error;
|
|
||||||
if(actual_out_len != out_len)
|
|
||||||
goto error;
|
|
||||||
} else {
|
|
||||||
for(size_t i = 0; i < in_len; ++i)
|
|
||||||
dst[i] = src[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
src += in_len;
|
|
||||||
src_len -= in_len;
|
|
||||||
dst += out_len;
|
|
||||||
dst_ilen -= out_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* subtract leftover number of bytes to get size of compressed output */
|
|
||||||
*dst_len = buffer->size - dst_ilen;
|
|
||||||
return buffer;
|
|
||||||
|
|
||||||
error:
|
|
||||||
jz_buffer_free(buffer);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int m3k_stage1(jz_usbdev* dev, jz_buffer* buf)
|
|
||||||
{
|
{
|
||||||
int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data);
|
int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data);
|
||||||
if(rc < 0)
|
if(rc < 0)
|
||||||
|
@ -141,7 +35,7 @@ static int m3k_stage1(jz_usbdev* dev, jz_buffer* buf)
|
||||||
return jz_usb_start1(dev, 0xf4001800);
|
return jz_usb_start1(dev, 0xf4001800);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int m3k_stage2(jz_usbdev* dev, jz_buffer* buf)
|
static int run_stage2(jz_usbdev* dev, jz_buffer* buf)
|
||||||
{
|
{
|
||||||
int rc = jz_usb_send(dev, 0x80004000, buf->size, buf->data);
|
int rc = jz_usb_send(dev, 0x80004000, buf->size, buf->data);
|
||||||
if(rc < 0)
|
if(rc < 0)
|
||||||
|
@ -154,8 +48,8 @@ static int m3k_stage2(jz_usbdev* dev, jz_buffer* buf)
|
||||||
return jz_usb_start2(dev, 0x80004000);
|
return jz_usb_start2(dev, 0x80004000);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int m3k_get_file(jz_context* jz, mtar_t* tar, const char* file,
|
static int get_file(jz_context* jz, mtar_t* tar, const char* file,
|
||||||
bool decompress, jz_buffer** buf)
|
bool decompress, jz_buffer** buf)
|
||||||
{
|
{
|
||||||
jz_buffer* buffer = NULL;
|
jz_buffer* buffer = NULL;
|
||||||
mtar_header_t h;
|
mtar_header_t h;
|
||||||
|
@ -180,7 +74,7 @@ static int m3k_get_file(jz_context* jz, mtar_t* tar, const char* file,
|
||||||
|
|
||||||
if(decompress) {
|
if(decompress) {
|
||||||
uint32_t dst_len;
|
uint32_t dst_len;
|
||||||
jz_buffer* nbuf = ucl_unpack(buffer->data, buffer->size, &dst_len);
|
jz_buffer* nbuf = jz_ucl_unpack(buffer->data, buffer->size, &dst_len);
|
||||||
jz_buffer_free(buffer);
|
jz_buffer_free(buffer);
|
||||||
if(!nbuf) {
|
if(!nbuf) {
|
||||||
jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file);
|
jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file);
|
||||||
|
@ -196,7 +90,7 @@ static int m3k_get_file(jz_context* jz, mtar_t* tar, const char* file,
|
||||||
return JZ_SUCCESS;
|
return JZ_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int m3k_show_version(jz_context* jz, jz_buffer* info_file)
|
static int show_version(jz_context* jz, jz_buffer* info_file)
|
||||||
{
|
{
|
||||||
/* Extract the version string and log it for informational purposes */
|
/* Extract the version string and log it for informational purposes */
|
||||||
char* boot_version = (char*)info_file->data;
|
char* boot_version = (char*)info_file->data;
|
||||||
|
@ -211,17 +105,28 @@ static int m3k_show_version(jz_context* jz, jz_buffer* info_file)
|
||||||
return JZ_SUCCESS;
|
return JZ_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \brief Load the Rockbox bootloader on the FiiO M3K
|
/** \brief Load the Rockbox bootloader on an X1000 device
|
||||||
* \param dev USB device freshly returned by jz_usb_open()
|
* \param dev USB device freshly returned by jz_usb_open()
|
||||||
* \param filename Path to the "bootloader.m3k" file
|
* \param filename Path to the "bootloader.target" file
|
||||||
* \return either JZ_SUCCESS or an error code
|
* \return either JZ_SUCCESS or an error code
|
||||||
*/
|
*/
|
||||||
int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename)
|
int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename)
|
||||||
{
|
{
|
||||||
|
const jz_device_info* dev_info;
|
||||||
|
char spl_filename[32];
|
||||||
jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL;
|
jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL;
|
||||||
mtar_t tar;
|
mtar_t tar;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
/* In retrospect using a model-dependent archive format was not a good
|
||||||
|
* idea, but it's not worth fixing just yet... */
|
||||||
|
dev_info = jz_get_device_info(type);
|
||||||
|
if(!dev_info)
|
||||||
|
return JZ_ERR_OTHER;
|
||||||
|
/* use of sprintf is safe since file_ext is short */
|
||||||
|
sprintf(spl_filename, "spl.%s", dev_info->file_ext);
|
||||||
|
|
||||||
|
/* Now open the archive */
|
||||||
rc = mtar_open(&tar, filename, "r");
|
rc = mtar_open(&tar, filename, "r");
|
||||||
if(rc != MTAR_ESUCCESS) {
|
if(rc != MTAR_ESUCCESS) {
|
||||||
jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", filename, rc);
|
jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", filename, rc);
|
||||||
|
@ -229,25 +134,25 @@ int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Extract all necessary files */
|
/* Extract all necessary files */
|
||||||
rc = m3k_get_file(dev->jz, &tar, "spl.m3k", false, &spl);
|
rc = get_file(dev->jz, &tar, spl_filename, false, &spl);
|
||||||
if(rc != JZ_SUCCESS)
|
if(rc != JZ_SUCCESS)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
rc = m3k_get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader);
|
rc = get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader);
|
||||||
if(rc != JZ_SUCCESS)
|
if(rc != JZ_SUCCESS)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
rc = m3k_get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
|
rc = get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
|
||||||
if(rc != JZ_SUCCESS)
|
if(rc != JZ_SUCCESS)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
/* Display the version string */
|
/* Display the version string */
|
||||||
rc = m3k_show_version(dev->jz, info_file);
|
rc = show_version(dev->jz, info_file);
|
||||||
if(rc != JZ_SUCCESS)
|
if(rc != JZ_SUCCESS)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
/* Stage1 boot of SPL to set up hardware */
|
/* Stage1 boot of SPL to set up hardware */
|
||||||
rc = m3k_stage1(dev, spl);
|
rc = run_stage1(dev, spl);
|
||||||
if(rc != JZ_SUCCESS)
|
if(rc != JZ_SUCCESS)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
@ -256,7 +161,7 @@ int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename)
|
||||||
|
|
||||||
/* Stage2 boot into the bootloader's recovery menu
|
/* Stage2 boot into the bootloader's recovery menu
|
||||||
* User has to take manual action from there */
|
* User has to take manual action from there */
|
||||||
rc = m3k_stage2(dev, bootloader);
|
rc = run_stage2(dev, bootloader);
|
||||||
if(rc != JZ_SUCCESS)
|
if(rc != JZ_SUCCESS)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
Loading…
Reference in New Issue