Skip to content

Instantly share code, notes, and snippets.

@profi200
Last active September 13, 2017 23:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save profi200/038d112e1968b03fdda80754a9374731 to your computer and use it in GitHub Desktop.
Save profi200/038d112e1968b03fdda80754a9374731 to your computer and use it in GitHub Desktop.
/*
* Action Replay DSi flash tool.
* Copyright (C) 2017 derrek, profi200
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <getopt.h>
#include <libusb.h>
#define VENDOR_ID (0x1C1A)
#define PRODUCT_ID (0x0100)
#define FLASH_PAGE_SIZE (0x1000)
#define FLASH_SIZE (0x200000)
#define USB_ENDPOINT_OUT (0x01)
#define USB_ENDPOINT_IN (0x81)
#define USB_TIMEOUT (5000)
static void help(void)
{
printf("AR DSi flash tool by derrek/profi200\n"
"Usage: ardsiflash [OPTION...] [file path]\n\n"
" -r --read Read the flash. This is the default action\n"
" -w --write Write the flash\n"
" -o --offset=<hex> The offset to read or write. Default 0\n"
" -s --size=<hex> The size to read/write. Default 0x200000 bytes\n"
" -h --help Give this help list\n\n");
}
libusb_device_handle* findSetupDevice(uint16_t idVendor, uint16_t idProduct)
{
libusb_device **list;
ssize_t num = libusb_get_device_list(NULL, &list);
if(num < 0)
{
fprintf(stderr, "Failed to get device list.\n");
return NULL;
}
libusb_device_handle *devH = NULL;
for(uint32_t i = 0; i < num; i++)
{
struct libusb_device_descriptor desc;
if(libusb_get_device_descriptor(list[i], &desc) < 0)
{
fprintf(stderr, "Failed to get device descriptor.\n");
libusb_free_device_list(list, 1);
return devH;
}
if(desc.idVendor == idVendor && desc.idProduct == idProduct)
{
if(libusb_open(list[i], &devH)) fprintf(stderr, "Failed to open device.\n");
else
{
if(libusb_set_configuration(devH, 1))
{
fprintf(stderr, "Failed to set configuration\n");
libusb_close(devH);
devH = NULL;
}
}
break;
}
}
libusb_free_device_list(list, 1);
return devH;
}
bool ardsiCmd69(libusb_device_handle *devH)
{
static uint8_t cmd[4] = {0x69, 0x00, 0x00, 0x00};
int transferred;
if(libusb_bulk_transfer(devH, USB_ENDPOINT_OUT, cmd, 4, &transferred, USB_TIMEOUT) ||
transferred != 4)
{
fprintf(stderr, "Failed to send cmd 69.\n");
return false;
}
uint32_t cmd69Resp = 0;
if(libusb_bulk_transfer(devH, USB_ENDPOINT_IN, (unsigned char*)&cmd69Resp, 3, &transferred, USB_TIMEOUT) ||
transferred != 3)
{
fprintf(stderr, "Failed to get response to cmd 69.\n");
return false;
}
printf("Cmd 69 response: 0x%" PRIX32 "\n", cmd69Resp);
return true;
}
bool ardsiReadFlashPage(libusb_device_handle *devH, uint8_t *out, uint32_t offset)
{
uint8_t cmd[5];
cmd[0] = 0x72;
cmd[1] = (offset & 0xFF);
cmd[2] = (offset>>8) & 0xFF;
cmd[3] = (offset>>16) & 0xFF;
cmd[4] = offset>>24;
int transferred;
if(libusb_bulk_transfer(devH, USB_ENDPOINT_OUT, cmd, 5, &transferred, USB_TIMEOUT) ||
transferred != 5)
{
fprintf(stderr, "\nFailed to send page read command.\n");
return false;
}
if(libusb_bulk_transfer(devH, USB_ENDPOINT_IN, out, FLASH_PAGE_SIZE, &transferred, USB_TIMEOUT) ||
transferred != FLASH_PAGE_SIZE)
{
fprintf(stderr, "\nFailed to read flash page.\n");
return false;
}
return true;
}
bool ardsiWriteFlashPage(libusb_device_handle *devH, uint8_t *in, uint32_t offset)
{
uint8_t cmd[5];
cmd[0] = 0x65;
cmd[1] = (offset & 0xFF);
cmd[2] = (offset>>8) & 0xFF;
cmd[3] = (offset>>16) & 0xFF;
cmd[4] = offset>>24;
int transferred;
if(libusb_bulk_transfer(devH, USB_ENDPOINT_OUT, cmd, 5, &transferred, USB_TIMEOUT) ||
transferred != 5)
{
fprintf(stderr, "\nFailed to send page erase command.\n");
return false;
}
cmd[0] = 0x70;
if(libusb_bulk_transfer(devH, USB_ENDPOINT_OUT, cmd, 5, &transferred, USB_TIMEOUT) ||
transferred != 5)
{
fprintf(stderr, "\nFailed to send page write command.\n");
return false;
}
if(libusb_bulk_transfer(devH, USB_ENDPOINT_OUT, in, FLASH_PAGE_SIZE, &transferred, USB_TIMEOUT) ||
transferred != FLASH_PAGE_SIZE)
{
fprintf(stderr, "\nFailed to write flash page.\n");
return false;
}
return true;
}
int main(int argc, char *const argv[])
{
static const struct option long_options[] =
{{"read", no_argument, 0, 'r'},
{"write", no_argument, 0, 'w'},
{"offset", required_argument, 0, 'o'},
{"size", required_argument, 0, 's'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
bool read = true;
uint32_t offset = 0;
uint32_t size = FLASH_SIZE;
while(1)
{
int c = getopt_long(argc, argv, "rwo:s:", long_options, 0);
if(c == -1) break;
switch(c)
{
case 'r':
read = true;
break;
case 'w':
read = false;
break;
case 'o':
offset = strtoul(optarg, NULL, 16);
break;
case 's':
size = strtoul(optarg, NULL, 16);
break;
case 'h':
help();
return 0;
break;
case '?':
default:
help();
return 1;
}
}
if(argc - optind < 1 || argc - optind > 1)
{
help();
return 1;
}
const char *path = argv[optind];
if(offset & (FLASH_PAGE_SIZE - 1))
{
fprintf(stderr, "Warning. Unaligned offset. Rounding up.\n");
offset = (offset + FLASH_PAGE_SIZE - 1) & ~(FLASH_PAGE_SIZE - 1);
}
if(size & (FLASH_PAGE_SIZE - 1))
{
fprintf(stderr, "Warning. Unaligned size. Rounding up.\n");
size = (size + FLASH_PAGE_SIZE - 1) & ~(FLASH_PAGE_SIZE - 1);
}
if(offset > FLASH_SIZE || size > FLASH_SIZE || offset + size > FLASH_SIZE)
{
fprintf(stderr, "Warning. Offset and/or size are beyond the max of 0x200000\n");
}
if(libusb_init(NULL))
{
fprintf(stderr, "libusb init failed.\n");
return 2;
}
libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_INFO);
libusb_device_handle *devH = findSetupDevice(VENDOR_ID, PRODUCT_ID);
if(!devH)
{
fprintf(stderr, "Action Replay DSi not found.\n");
libusb_exit(NULL);
return 3;
}
if(libusb_claim_interface(devH, 0))
{
fprintf(stderr, "Failed to claim interface 0.\n");
libusb_close(devH);
libusb_exit(NULL);
return 4;
}
if(!ardsiCmd69(devH))
{
libusb_release_interface(devH, 0);
libusb_close(devH);
libusb_exit(NULL);
return 5;
}
uint8_t *buf = malloc(size);
if(!buf)
{
fprintf(stderr, "Not enough memory.\n");
libusb_release_interface(devH, 0);
libusb_close(devH);
libusb_exit(NULL);
return 6;
}
FILE *f = fopen(path, read ? "wb" : "rb");
if(!f)
{
fprintf(stderr, "Failed to open/create file.\n");
free(buf);
libusb_release_interface(devH, 0);
libusb_close(devH);
libusb_exit(NULL);
return 7;
}
if(!read)
{
if(fread(buf, 1, size, f) != size)
{
fprintf(stderr, "Failed to read file.\n");
fclose(f);
free(buf);
libusb_release_interface(devH, 0);
libusb_close(devH);
libusb_exit(NULL);
return 8;
}
}
uint8_t *bufPtr = buf;
for(uint32_t i = offset; i < offset + size; i += FLASH_PAGE_SIZE)
{
printf("Offset 0x%" PRIX32 "/0x%" PRIX32 "\r", i, offset + size);
if(read)
{
if(!ardsiReadFlashPage(devH, bufPtr, i))
{
fclose(f);
free(buf);
libusb_release_interface(devH, 0);
libusb_close(devH);
libusb_exit(NULL);
return 9;
}
}
else
{
if(!ardsiWriteFlashPage(devH, bufPtr, i))
{
fclose(f);
free(buf);
libusb_release_interface(devH, 0);
libusb_close(devH);
libusb_exit(NULL);
return 9;
}
}
bufPtr += FLASH_PAGE_SIZE;
}
printf("\n");
if(read)
{
if(fwrite(buf, 1, size, f) != size)
{
fprintf(stderr, "Failed to write file.\n");
fclose(f);
free(buf);
libusb_release_interface(devH, 0);
libusb_close(devH);
libusb_exit(NULL);
return 8;
}
}
printf("Successfully read/written data.\n");
fclose(f);
free(buf);
libusb_release_interface(devH, 0);
libusb_close(devH);
libusb_exit(NULL);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment