Last active
September 13, 2017 23:04
-
-
Save profi200/038d112e1968b03fdda80754a9374731 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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