├── .gitignore ├── menu.gb ├── menu.gbc ├── multirom.h ├── Makefile ├── AUTHORS ├── ems.h ├── main.h ├── COPYING ├── README ├── multirom.c ├── ems.c └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | ems-flasher 2 | *.o 3 | -------------------------------------------------------------------------------- /menu.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stewmath/ems-flasher/HEAD/menu.gb -------------------------------------------------------------------------------- /menu.gbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stewmath/ems-flasher/HEAD/menu.gbc -------------------------------------------------------------------------------- /multirom.h: -------------------------------------------------------------------------------- 1 | void readRoms(int bank); 2 | void listRoms(); 3 | int addRom(const char* filename); 4 | int deleteRom(int id); 5 | 6 | int setPaletteRom(const char* filename); 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROG = ems-flasher 2 | OBJS = ems.o main.o multirom.o 3 | 4 | CFLAGS = -g -Wall -Werror -pedantic -std=c99 5 | CFLAGS += `pkg-config --cflags libusb-1.0` 6 | 7 | all: $(PROG) 8 | 9 | $(PROG): $(OBJS) 10 | $(CC) -o $(PROG) $(OBJS) `pkg-config --libs libusb-1.0` 11 | 12 | install: $(PROG) 13 | install ems-flasher /usr/local/bin 14 | 15 | clean: 16 | rm -f $(PROG) $(OBJS) 17 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Mike Ryan 2 | 3 | - initial reverse engineering 4 | - single bank read and write 5 | - SRAM read 6 | - ROM title read 7 | 8 | David Wendt JR. 9 | 10 | - reverse engineered later part of protocol 11 | - multiple bank read and write 12 | - SRAM write 13 | - full ROM header read 14 | - checksum validation 15 | -------------------------------------------------------------------------------- /ems.h: -------------------------------------------------------------------------------- 1 | #ifndef __EMS_H__ 2 | #define __EMS_H__ 3 | 4 | #include 5 | #include 6 | 7 | int ems_init(void); 8 | 9 | int ems_read(int from, uint32_t offset, unsigned char *buf, size_t count); 10 | int ems_write(int to, uint32_t offset, unsigned char *buf, size_t count); 11 | 12 | #define FROM_ROM 1 13 | #define FROM_SRAM 2 14 | #define TO_ROM FROM_ROM 15 | #define TO_SRAM FROM_SRAM 16 | 17 | #endif /* __EMS_H__ */ 18 | -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | /* options */ 2 | typedef struct _options_t { 3 | int verbose; 4 | int blocksize; 5 | int mode; 6 | char *file; 7 | int id; 8 | int bank; 9 | int space; 10 | } options_t; 11 | 12 | extern options_t opts; 13 | 14 | extern const unsigned char nintylogo[0x30]; 15 | 16 | int getRomSize(int sizeCode); 17 | 18 | //offsets to parts of the cart header 19 | enum headeroffsets { 20 | HEADER_LOGO = 0x104, 21 | HEADER_TITLE = 0x134, 22 | HEADER_CGBFLAG = 0x143, 23 | HEADER_SGBFLAG = 0x146, 24 | HEADER_ROMSIZE = 0x148, 25 | HEADER_RAMSIZE = 0x149, 26 | HEADER_REGION = 0x14A, 27 | HEADER_OLDLICENSEE = 0x14B, 28 | HEADER_ROMVER = 0x14C, 29 | HEADER_CHKSUM = 0x14D, 30 | }; 31 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 by Mike Ryan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | The EMS flasher is a simple command line flasher for the 64 Mbit EMS USB 2 | flash cart for Game Boy. 3 | 4 | This software was written by Mike Ryan 5 | 6 | For more information, see the web site at: 7 | http://lacklustre.net/gb/ems/ 8 | 9 | BUILDING 10 | 11 | make 12 | 13 | On Mac, prior to building you must install pkgconfig and libusb from 14 | ports, like so: 15 | 16 | sudo port install pkgconfig 17 | sudo port install libusb 18 | 19 | (thanks to hyarion for this info) 20 | 21 | RUNNING 22 | 23 | The software has three major modes of operation: 24 | 25 | * write ROM to cart 26 | * read ROM from cart 27 | * read title of ROM on cart 28 | 29 | To write use --write, to read use --read, and to get the title use 30 | --title. 31 | 32 | Write mode will write the ROM specified on the command line to bank 1 on 33 | the cart. Read mode will read the entirety of bank 1 (32 megabits / 4 34 | megabytes) into the ROM file specified. 35 | 36 | Title mode does not require a file argument, and will print the ROM 37 | title to stdout. 38 | 39 | BEWARE: if you give the EMS flasher a huge file for writing, it will 40 | continue writing past the end of the cart and do unknown amounts of 41 | damage. Please don't do this! 42 | 43 | Additionally, all modes take a --verbose flag for giving more output. 44 | You can also adjust the block size, but it is recommended you leave this 45 | to the default of 4096 bytes for writing and 32 bytes for reading (used 46 | by the Windows software). 47 | 48 | For a full list of options, run the command with the --help flag. 49 | 50 | MULTIROM SUPPORT 51 | 52 | The options "--add", "--delete", and "--list" handle multirom support. See 53 | examples for usage. 54 | 55 | EXAMPLES 56 | 57 | # write the ROM to the cart 58 | ./ems-flasher --write totally_legit_rom.gb 59 | 60 | # saves the contents of the cart into the file; print some extra info 61 | ./ems-flasher --verbose --read not_warez.gb 62 | 63 | # print out the title 64 | ./ems-flasher --title 65 | 66 | MULTIROM EXAMPLES 67 | 68 | # Setup the bank for gameboy [color] mode 69 | ./ems-flasher --write menu.gb[c] 70 | 71 | # Add a rom to the bank 72 | ./ems-flasher --add totally_legit_rom.gb 73 | 74 | # Get a list of roms in the bank 75 | ./ems-flasher --list 76 | 77 | # Remove the first rom from the bank 78 | ./ems-flasher --delete 1 79 | 80 | BUGS 81 | 82 | Preferably use the bug tracker found at the web site (at the top of this 83 | doc) to report any bugs. 84 | 85 | You can also send em to mikeryan \at lacklustre.net 86 | -------------------------------------------------------------------------------- /multirom.c: -------------------------------------------------------------------------------- 1 | #include "multirom.h" 2 | #include "ems.h" 3 | #include "main.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define BANK_SIZE 0x400000 11 | #define BANK_SIZE_MASK 0x3fffff 12 | 13 | typedef struct Rom { 14 | int offset; 15 | char title[0xf]; 16 | int size; 17 | } Rom; 18 | 19 | Rom* roms[0x80]; 20 | int numRoms = 0; 21 | 22 | 23 | int roundup(int offset, int size) { 24 | if (offset%size == 0) 25 | return offset; 26 | return offset - (offset%size) + size; 27 | } 28 | 29 | uint8_t getChecksum(uint8_t* header) { 30 | unsigned char checksum = 0; 31 | for (int i = 0x34; i <= 0x4c; i++) { 32 | checksum = checksum - header[i] - 1; 33 | } 34 | //printf("Checksum %.2x\n", checksum); 35 | return checksum; 36 | } 37 | 38 | void readRoms(int bank) { 39 | unsigned char buf[512]; 40 | 41 | numRoms = 0; 42 | 43 | for (int i=0; ititle, buf+0x034, 0xe); 57 | rom->title[0xe] = '\0'; 58 | rom->size = getRomSize(buf[0x048]); 59 | rom->offset = bank*BANK_SIZE+i*0x8000; 60 | 61 | roms[numRoms++] = rom; 62 | } 63 | } 64 | 65 | } 66 | 67 | void listRoms() { 68 | for (int i=0; ititle); 70 | if (opts.verbose) 71 | printf(" (offset 0x%x, size 0x%x)", roms[i]->offset, roms[i]->size); 72 | printf("\n"); 73 | } 74 | } 75 | 76 | int addRom(const char* filename) { 77 | int blocksize = opts.blocksize; 78 | unsigned char buf[512]; 79 | int offset=0; 80 | 81 | FILE *write_file = fopen(filename, "rb"); 82 | 83 | if (write_file == NULL) { 84 | err(1, "Can't open ROM file %s", opts.file); 85 | } 86 | 87 | fseek(write_file, 0, SEEK_END); 88 | int size = ftell(write_file); 89 | fseek(write_file, 0, SEEK_SET); 90 | 91 | if (numRoms == 0) 92 | offset = opts.bank*BANK_SIZE; 93 | else { 94 | for (int i=0; ioffset + roms[i]->size; 97 | int nextTry = roundup(romEnd, size); 98 | if (BANK_SIZE - (nextTry&BANK_SIZE_MASK) >= size) { 99 | offset = nextTry; 100 | break; 101 | } 102 | else { 103 | err(1, "Not enough space\n"); 104 | return 1; 105 | } 106 | } 107 | else { 108 | int nextTry = roundup(roms[i]->offset+roms[i]->size, size); 109 | if (roms[i+1]->offset - nextTry >= size) { 110 | offset = nextTry; 111 | break; 112 | } 113 | } 114 | } 115 | } 116 | 117 | int base = offset/BANK_SIZE * BANK_SIZE; 118 | offset &= BANK_SIZE_MASK; 119 | 120 | if (base/BANK_SIZE != opts.bank) { 121 | err(1, "Not enough space\n"); 122 | return 1; 123 | } 124 | 125 | while (offset + blocksize <= BANK_SIZE && 126 | fread(buf, blocksize, 1, write_file) == 1) { 127 | int r = ems_write(TO_ROM, offset+base, buf, blocksize); 128 | if (r < 0) { 129 | warnx("can't write %d bytes at offset %u", blocksize, offset); 130 | return 1; 131 | } 132 | 133 | offset += blocksize; 134 | } 135 | 136 | fclose(write_file); 137 | 138 | if (opts.verbose) 139 | printf("Wrote ROM to 0x%x\n", offset&BANK_SIZE_MASK); 140 | 141 | 142 | return 0; 143 | } 144 | 145 | int deleteRom(int id) { 146 | unsigned char buf[512]; 147 | 148 | if (id >= numRoms || id < 0) 149 | return 1; 150 | memset(buf, 0, 512); 151 | 152 | int blocksize = opts.blocksize; 153 | 154 | for (int i=0; i<512/blocksize; i++) 155 | ems_write(TO_ROM, roms[id]->offset+i*blocksize, buf+i*blocksize, blocksize); 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /ems.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include // FIXME this will (probably) go away with error coes 6 | #include 7 | #include 8 | #include 9 | #include /* for htonl */ 10 | 11 | #include 12 | 13 | #include "ems.h" 14 | 15 | /* magic numbers! */ 16 | #define EMS_VID 0x4670 17 | #define EMS_PID 0x9394 18 | 19 | #define EMS_EP_SEND (2 | LIBUSB_ENDPOINT_OUT) 20 | #define EMS_EP_RECV (1 | LIBUSB_ENDPOINT_IN) 21 | 22 | enum { 23 | CMD_READ = 0xff, 24 | CMD_WRITE = 0x57, 25 | CMD_READ_SRAM = 0x6d, 26 | CMD_WRITE_SRAM = 0x4d, 27 | }; 28 | 29 | static struct libusb_device_handle *devh = NULL; 30 | static int claimed = 0; 31 | 32 | /** 33 | * Attempt to find the EMS cart by vid/pid. 34 | * 35 | * Returns: 36 | * 0 success 37 | * < 0 failure 38 | */ 39 | static int find_ems_device(void) { 40 | ssize_t num_devices = 0; 41 | libusb_device **device_list = NULL; 42 | struct libusb_device_descriptor device_descriptor; 43 | int i = 0; 44 | int retval = 0; 45 | 46 | num_devices = libusb_get_device_list(NULL, &device_list); 47 | if (num_devices >= 0) { 48 | for (; i < num_devices; ++i) { 49 | (void) memset(&device_descriptor, 0, sizeof(device_descriptor)); 50 | retval = libusb_get_device_descriptor(device_list[i], &device_descriptor); 51 | if (retval == 0) { 52 | if (device_descriptor.idVendor == EMS_VID 53 | && device_descriptor.idProduct == EMS_PID) { 54 | retval = libusb_open(device_list[i], &devh); 55 | if (retval != 0) { 56 | /* 57 | * According to the documentation, devh will not 58 | * be populated on error, so it should remain 59 | * NULL. 60 | */ 61 | fprintf(stderr, "Failed to open device (libusb error: %s).\n", libusb_error_name(retval)); 62 | #ifdef __linux__ 63 | if (retval == LIBUSB_ERROR_ACCESS) { 64 | fprintf(stderr, "Try running as root/sudo or update udev rules (check the FAQ for more info).\n"); 65 | } 66 | #endif 67 | } 68 | break; 69 | } 70 | } else { 71 | fprintf(stderr, "Failed to get device description (libusb error: %s).\n", libusb_error_name(retval)); 72 | } 73 | } 74 | if (i == num_devices) { 75 | fprintf(stderr, "Could not find device, is it plugged in?\n"); 76 | } 77 | libusb_free_device_list(device_list, 1); 78 | device_list = NULL; 79 | } else { 80 | fprintf(stderr, "Failed to get device list: %s\n", libusb_error_name((int)num_devices)); 81 | } 82 | 83 | return devh != NULL ? 0 : -EIO; 84 | } 85 | 86 | /** 87 | * Init the flasher. Inits libusb and claims the device. Aborts if libusb 88 | * can't be initialized. 89 | * 90 | * TODO replace printed error with return code 91 | * 92 | * Returns: 93 | * 0 Success 94 | * < 0 Failure 95 | */ 96 | int ems_init(void) { 97 | int r; 98 | void ems_deinit(void); 99 | 100 | // call the cleanup when we're done 101 | atexit(ems_deinit); 102 | 103 | r = libusb_init(NULL); 104 | if (r < 0) { 105 | fprintf(stderr, "failed to initialize libusb\n"); 106 | exit(1); // pretty much hosed 107 | } 108 | 109 | r = find_ems_device(); 110 | if (r < 0) { 111 | return r; 112 | } 113 | 114 | r = libusb_claim_interface(devh, 0); 115 | if (r < 0) { 116 | fprintf(stderr, "usb_claim_interface error %d\n", r); 117 | return r; 118 | } 119 | 120 | claimed = 1; 121 | return 0; 122 | } 123 | 124 | /** 125 | * Cleanup / release the device. Registered with atexit. 126 | */ 127 | void ems_deinit(void) { 128 | if (claimed) 129 | libusb_release_interface(devh, 0); 130 | 131 | libusb_close(devh); 132 | libusb_exit(NULL); 133 | } 134 | 135 | /** 136 | * Initialize a command buffer. Commands are a 1 byte command code followed by 137 | * a 4 byte address and a 4 byte value. 138 | * 139 | * buf must point to a memory chunk of size >= 9 bytes 140 | */ 141 | static void ems_command_init( 142 | unsigned char *buf, // buffer to init 143 | unsigned char cmd, // command to run 144 | uint32_t addr, // address 145 | uint32_t val // value 146 | ) { 147 | buf[0] = cmd; 148 | *(uint32_t *)(buf + 1) = htonl(addr); 149 | *(uint32_t *)(buf + 5) = htonl(val); 150 | } 151 | 152 | /** 153 | * Read some bytes from the cart. 154 | * 155 | * Params: 156 | * from FROM_ROM or FROM_SRAM 157 | * offset absolute read address from the cart 158 | * buf buffer to read into (buffer must be at least count bytes) 159 | * count number of bytes to read 160 | * 161 | * Returns: 162 | * >= 0 number of bytes read (will always == count) 163 | * < 0 error sending command or reading data 164 | */ 165 | int ems_read(int from, uint32_t offset, unsigned char *buf, size_t count) { 166 | int r, transferred; 167 | unsigned char cmd; 168 | unsigned char cmd_buf[9]; 169 | 170 | assert(from == FROM_ROM || from == FROM_SRAM); 171 | 172 | cmd = from == FROM_ROM ? CMD_READ : CMD_READ_SRAM; 173 | ems_command_init(cmd_buf, cmd, offset, count); 174 | 175 | #ifdef DEBUG 176 | int i; 177 | for (i = 0; i < 9; ++i) 178 | printf("%02x ", cmd_buf[i]); 179 | printf("\n"); 180 | #endif 181 | 182 | // send the read command 183 | r = libusb_bulk_transfer(devh, EMS_EP_SEND, cmd_buf, sizeof(cmd_buf), &transferred, 0); 184 | if (r < 0) 185 | return r; 186 | 187 | // read the data 188 | r = libusb_bulk_transfer(devh, EMS_EP_RECV, buf, count, &transferred, 0); 189 | if (r < 0) 190 | return r; 191 | 192 | return transferred; 193 | } 194 | 195 | /** 196 | * Write to the cartridge. 197 | * 198 | * Params: 199 | * to TO_ROM or TO_SRAM 200 | * offset address to write to 201 | * buf data to write 202 | * count number of bytes out of buf to write 203 | * 204 | * Returns: 205 | * >= 0 number of bytes written (will always == count) 206 | * < 0 error writing data 207 | */ 208 | int ems_write(int to, uint32_t offset, unsigned char *buf, size_t count) { 209 | int r, transferred; 210 | unsigned char cmd; 211 | unsigned char *write_buf; 212 | 213 | assert(to == TO_ROM || to == TO_SRAM); 214 | cmd = to == TO_ROM ? CMD_WRITE : CMD_WRITE_SRAM; 215 | 216 | // thx libusb for having no scatter/gather io 217 | write_buf = malloc(count + 9); 218 | if (write_buf == NULL) 219 | err(1, "malloc"); 220 | 221 | // set up the command buffer 222 | ems_command_init(write_buf, cmd, offset, count); 223 | memcpy(write_buf + 9, buf, count); 224 | 225 | r = libusb_bulk_transfer(devh, EMS_EP_SEND, write_buf, count + 9, &transferred, 0); 226 | if (r == 0) 227 | r = transferred; // return number of bytes sent on success 228 | 229 | free(write_buf); 230 | 231 | return r; 232 | } 233 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ems.h" 9 | #include "multirom.h" 10 | #include "main.h" 11 | 12 | // don't forget to bump this :P 13 | #define VERSION "0.02" 14 | 15 | // one bank is 32 megabits 16 | #define BANK_SIZE 0x400000 17 | #define SRAM_SIZE 0x020000 18 | 19 | const int limits[3] = {0, BANK_SIZE, SRAM_SIZE}; 20 | 21 | // operation mode 22 | #define MODE_READ 1 23 | #define MODE_WRITE 2 24 | #define MODE_TITLE 3 25 | #define MODE_LIST 4 26 | #define MODE_ADD 5 27 | #define MODE_DELETE 6 28 | 29 | // defaults 30 | options_t opts = { 31 | .verbose = 0, 32 | .blocksize = 0, 33 | .mode = 0, 34 | .file = NULL, 35 | .id = 0, 36 | .bank = 0, 37 | .space = 0, 38 | }; 39 | 40 | // NXXXXXXX logo ;) 41 | const unsigned char nintylogo[0x30] = 42 | {0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 43 | 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 44 | 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 45 | 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 46 | 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 47 | 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E}; 48 | 49 | // default blocksizes 50 | #define BLOCKSIZE_READ 4096 51 | #define BLOCKSIZE_WRITE 32 52 | 53 | /** 54 | * Usage 55 | */ 56 | void usage(char *name) { 57 | printf("Usage: %s < --read | --write | --add > [ --verbose ] \n", name); 58 | printf(" %s --list\n", name); 59 | printf(" %s --delete \n", name); 60 | printf(" %s --title\n", name); 61 | printf(" %s --version\n", name); 62 | printf(" %s --help\n", name); 63 | printf("Writes a ROM or SAV file to the EMS 64 Mbit USB flash cart\n\n"); 64 | printf("Options:\n"); 65 | printf(" --read read entire cart into file\n"); 66 | printf(" --write write ROM file to cart\n"); 67 | printf(" --title title of the ROM in both banks\n"); 68 | printf(" --verbose displays more information\n"); 69 | printf(" --bank select cart bank (1 or 2)\n"); 70 | printf(" --save force write to SRAM\n"); 71 | printf(" --rom force write to Flash ROM\n"); 72 | printf(" --list list the ROMs on the cartridge\n"); 73 | printf(" --add add a ROM to the nearest space it will fit\n"); 74 | printf(" --delete remove a ROM from the cartridge from the cartridge\n"); 75 | printf("\n"); 76 | //printf("You MUST supply exactly one of --read, --write, or --title\n"); 77 | printf("Reading or writing with a file ending in .sav will write to SRAM.\n"); 78 | printf("To select between ROM and SRAM, use ONE of the --save / --rom options.\n"); 79 | printf("\n"); 80 | printf("Advanced options:\n"); 81 | printf(" --blocksize bytes per block (default: 4096 read, 32 write)\n"); 82 | printf("\n"); 83 | printf("Written by Mike Ryan and others\n"); 84 | printf("See web site for more info:\n"); 85 | printf(" http://lacklustre.net/gb/ems/\n"); 86 | exit(1); 87 | } 88 | 89 | /** 90 | * Get the options to the binary. Options are stored in the global "opts". 91 | */ 92 | void get_options(int argc, char **argv) { 93 | int c, optval; 94 | 95 | while (1) { 96 | int option_index = 0; 97 | static struct option long_options[] = { 98 | {"help", 0, 0, 'h'}, 99 | {"version", 0, 0, 'V'}, 100 | {"verbose", 0, 0, 'v'}, 101 | {"read", 0, 0, 'r'}, 102 | {"write", 0, 0, 'w'}, 103 | {"title", 0, 0, 't'}, 104 | {"list", 0, 0, 'l'}, 105 | {"add", 0, 0, 'a'}, 106 | {"delete", 0, 0, 'd'}, 107 | {"blocksize", 1, 0, 's'}, 108 | {"bank", 1, 0, 'b'}, 109 | {"save", 0, 0, 'S'}, 110 | {"rom", 0, 0, 'R'}, 111 | {"setpal", 0, 0, 'P'}, 112 | {0, 0, 0, 0} 113 | }; 114 | 115 | c = getopt_long( 116 | argc, argv, "hVvs:rwt", 117 | long_options, &option_index 118 | ); 119 | if (c == -1) 120 | break; 121 | 122 | switch (c) { 123 | case 'h': 124 | usage(argv[0]); 125 | break; 126 | case 'V': 127 | printf("EMS-flasher %s\n", VERSION); 128 | exit(0); 129 | break; 130 | case 'v': 131 | opts.verbose = 1; 132 | break; 133 | case 'r': 134 | if (opts.mode != 0) goto mode_error; 135 | opts.mode = MODE_READ; 136 | break; 137 | case 'w': 138 | if (opts.mode != 0) goto mode_error; 139 | opts.mode = MODE_WRITE; 140 | break; 141 | case 't': 142 | if (opts.mode != 0) goto mode_error; 143 | opts.mode = MODE_TITLE; 144 | break; 145 | case 'l': 146 | if (opts.mode != 0) goto mode_error; 147 | opts.mode = MODE_LIST; 148 | break; 149 | case 'a': 150 | if (opts.mode != 0) goto mode_error; 151 | opts.mode = MODE_ADD; 152 | break; 153 | case 'd': 154 | if (opts.mode != 0) goto mode_error; 155 | opts.mode = MODE_DELETE; 156 | break; 157 | case 's': 158 | optval = atoi(optarg); 159 | if (optval <= 0) { 160 | printf("Error: block size must be > 0\n"); 161 | usage(argv[0]); 162 | } 163 | // TODO make sure it divides evenly into bank size 164 | opts.blocksize = optval; 165 | break; 166 | case 'b': 167 | optval = atoi(optarg); 168 | if (optval < 1 || optval > 2) { 169 | printf("Error: cart only has two banks 1 and 2\n"); 170 | usage(argv[0]); 171 | } 172 | opts.bank = optval - 1; 173 | break; 174 | case 'S': 175 | if (opts.space != 0) goto mode_error2; 176 | opts.space = FROM_SRAM; 177 | break; 178 | case 'R': 179 | if (opts.space != 0) goto mode_error2; 180 | opts.space = FROM_ROM; 181 | break; 182 | default: 183 | usage(argv[0]); 184 | break; 185 | } 186 | } 187 | 188 | // user didn't supply mode 189 | if (opts.mode == 0) 190 | goto mode_error; 191 | 192 | if (opts.mode == MODE_WRITE || opts.mode == MODE_READ || opts.mode == MODE_ADD) { 193 | // user didn't give a filename 194 | if (optind >= argc) { 195 | printf("Error: you must provide an %s filename\n", opts.mode == MODE_READ ? "output" : "input"); 196 | usage(argv[0]); 197 | } 198 | 199 | // extra argument: ROM file 200 | opts.file = argv[optind]; 201 | } 202 | else if (opts.mode == MODE_DELETE) { 203 | if (optind >= argc) { 204 | printf("Error: you must provide an id to delete (use --list to see id's)\n"); 205 | usage(argv[0]); 206 | } 207 | 208 | // extra argument: ROM file 209 | opts.id = atoi(argv[optind]); 210 | } 211 | 212 | // set a default blocksize if the user hasn't given one 213 | if (opts.blocksize == 0) 214 | opts.blocksize = opts.mode == MODE_READ ? BLOCKSIZE_READ : BLOCKSIZE_WRITE; 215 | 216 | return; 217 | 218 | mode_error: 219 | printf("Error: must supply exactly one of --read, --write, --title, --add, --delete, or --list\n"); 220 | usage(argv[0]); 221 | 222 | mode_error2: 223 | printf("Error: must supply zero or one of --sram, or --rom\n"); 224 | usage(argv[0]); 225 | } 226 | 227 | /** 228 | * Main 229 | */ 230 | int main(int argc, char **argv) { 231 | int r; 232 | 233 | get_options(argc, argv); 234 | 235 | if (opts.verbose) 236 | printf("trying to find EMS cart\n"); 237 | 238 | r = ems_init(); 239 | if (r < 0) 240 | return 1; 241 | 242 | if (opts.verbose) 243 | printf("claimed EMS cart\n"); 244 | 245 | // we'll need a buffer one way or another 246 | int blocksize = opts.blocksize; 247 | uint32_t offset = 0; 248 | uint32_t base = opts.bank * BANK_SIZE; 249 | if (opts.verbose) 250 | printf("base address is 0x%X\n", base); 251 | 252 | unsigned char *buf = malloc(blocksize); 253 | if (buf == NULL) 254 | err(1, "malloc"); 255 | 256 | // determine what we're reading/writing from/to 257 | int space = opts.space; 258 | if (space == 0 && opts.file != NULL) { 259 | //attempt to autodetect the file 260 | //are the last four characters .sav ? 261 | size_t namelen = strlen(opts.file); 262 | 263 | if (opts.file[namelen - 4] == '.' && 264 | tolower(opts.file[namelen - 3]) == 's' && 265 | tolower(opts.file[namelen - 2]) == 'a' && 266 | tolower(opts.file[namelen - 1]) == 'v') { 267 | space = FROM_SRAM; 268 | } else { 269 | space = FROM_ROM; 270 | } 271 | } 272 | 273 | // read the ROM and save it into the file 274 | if (opts.mode == MODE_READ) { 275 | FILE *save_file = fopen(opts.file, "w"); 276 | if (save_file == NULL) 277 | err(1, "Can't open %s for writing", opts.file); 278 | 279 | if (opts.verbose && space == FROM_ROM) 280 | printf("Saving ROM into %s\n", opts.file); 281 | else if (opts.verbose) 282 | printf("Saving SAVE into %s\n", opts.file); 283 | 284 | while ((offset + blocksize) <= limits[space]) { 285 | r = ems_read(space, offset + base, buf, blocksize); 286 | if (r < 0) { 287 | warnx("can't read %d bytes at offset %u\n", blocksize, offset); 288 | return 1; 289 | } 290 | 291 | r = fwrite(buf, blocksize, 1, save_file); 292 | if (r != 1) 293 | err(1, "can't write %d bytes into file at offset %u", blocksize, offset); 294 | 295 | offset += blocksize; 296 | } 297 | 298 | fclose(save_file); 299 | 300 | if (opts.verbose) 301 | printf("Successfully wrote %u bytes into %s\n", offset, opts.file); 302 | } 303 | 304 | // write ROM in the file to bank 1 305 | else if (opts.mode == MODE_WRITE) { 306 | FILE *write_file = fopen(opts.file, "r"); 307 | if (write_file == NULL) { 308 | if (space == TO_ROM) 309 | err(1, "Can't open ROM file %s", opts.file); 310 | else 311 | err(1, "Can't open SAVE file %s", opts.file); 312 | } 313 | 314 | if (opts.verbose && space == TO_ROM) 315 | printf("Writing ROM file %s\n", opts.file); 316 | else if (opts.verbose) 317 | printf("Writing SAVE file %s\n", opts.file); 318 | 319 | while ((offset + blocksize) <= limits[space] && fread(buf, blocksize, 1, write_file) == 1) { 320 | r = ems_write(space, offset + base, buf, blocksize); 321 | if (r < 0) { 322 | warnx("can't write %d bytes at offset %u", blocksize, offset); 323 | return 1; 324 | } 325 | 326 | offset += blocksize; 327 | } 328 | 329 | fclose(write_file); 330 | 331 | if (opts.verbose) 332 | printf("Successfully wrote %u from %s\n", offset, opts.file); 333 | } 334 | 335 | // read the ROM header 336 | else if (opts.mode == MODE_TITLE) { 337 | unsigned char buf[512]; 338 | int i; 339 | 340 | r = ems_read(FROM_ROM, 0, buf, 512); 341 | if (r < 0) 342 | errx(1, "Couldn't read ROM header at bank 0, offset 0, len 512\n"); 343 | 344 | printf("Bank 0: "); 345 | for (i = HEADER_TITLE; i < (HEADER_TITLE + 16); i++) { 346 | putchar(buf[i]); 347 | } 348 | printf("\nHardware support: "); 349 | 350 | if ((buf[HEADER_CGBFLAG] & 128) && (buf[HEADER_CGBFLAG] & 64)) { 351 | printf("CGB\n"); 352 | } else if ((buf[HEADER_CGBFLAG] & 128) && (buf[HEADER_CGBFLAG] & 64) && buf[HEADER_SGBFLAG] == 0x03) { 353 | printf("CGB <+SGB>, not real option set\n"); 354 | } else if ((buf[HEADER_CGBFLAG] & 128) && buf[HEADER_SGBFLAG] == 0x03) { 355 | printf("DMG <+CGB, +SGB>\n"); 356 | } else if ((buf[HEADER_CGBFLAG] & 128)) { 357 | printf("DMG <+CGB>\n"); 358 | } else if (buf[HEADER_SGBFLAG] == 0x03) { 359 | printf("DMG <+SGB>\n"); 360 | } else { 361 | printf("DMG\n"); 362 | } 363 | 364 | //Verify cartridge header checksum while we're at it 365 | uint8_t calculated_chk = 0; 366 | for (i = HEADER_TITLE; i < HEADER_CHKSUM; i++) { 367 | calculated_chk -= buf[i] + 1; 368 | } 369 | 370 | if (calculated_chk != buf[HEADER_CHKSUM]) { 371 | printf("Cartridge header checksum invalid. This game will NOT boot on real hardware.\n"); 372 | } 373 | 374 | if (buf[HEADER_SGBFLAG] == 0x03 && buf[HEADER_OLDLICENSEE] != 0x33) { 375 | printf("SGB functions were enabled, but Old Licensee field is not set to 33h. This game will not be able to use SGB functions on real hardware.\n"); 376 | } 377 | 378 | if (opts.verbose) { 379 | switch (buf[HEADER_ROMSIZE]) { 380 | case 0: 381 | case 1: 382 | case 2: 383 | case 3: 384 | case 4: 385 | case 5: 386 | case 6: 387 | case 7: 388 | printf("%u KB ROM\n", 32 << buf[HEADER_ROMSIZE]); 389 | break; 390 | case 0x52: 391 | printf("1152 KB ROM\n"); 392 | break; 393 | case 0x53: 394 | printf("1280 KB ROM\n"); 395 | break; 396 | case 0x54: 397 | printf("1536 KB ROM\n"); 398 | break; 399 | default: 400 | printf("Unknown ROM size code\n"); 401 | break; 402 | } 403 | } 404 | 405 | r = ems_read(FROM_ROM, BANK_SIZE, (unsigned char *)buf, 512); 406 | if (r < 0) 407 | errx(1, "Couldn't read ROM header at bank 1, offset 0, len 512\n"); 408 | 409 | printf("Bank 1: "); 410 | for (i = HEADER_TITLE; i < (HEADER_TITLE + 16); i++) { 411 | putchar(buf[i]); 412 | } 413 | printf("\nHardware support: "); 414 | 415 | if (buf[HEADER_CGBFLAG] == 0x80 && buf[HEADER_SGBFLAG] == 0x03) { 416 | printf("CGB enhanced, SGB enhanced, DMG compatible\n"); 417 | } else if (buf[HEADER_CGBFLAG] == 0x80) { 418 | printf("CGB enhanced, DMG compatible\n"); 419 | } else if (buf[HEADER_CGBFLAG] == 0xC0) { 420 | printf("CGB only\n"); 421 | } else if (buf[HEADER_CGBFLAG] == 0xC0 && buf[HEADER_SGBFLAG] == 0x03) { 422 | printf("CGB only, SGB enhanced (not a real set of options)\n"); 423 | } else if (buf[HEADER_SGBFLAG] == 0x03) { 424 | printf("DMG, SGB enhanced\n"); 425 | } else { 426 | printf("DMG\n"); 427 | } 428 | 429 | //Verify cartridge header checksum while we're at it 430 | calculated_chk = 0; 431 | for (i = HEADER_TITLE; i < HEADER_CHKSUM; i++) { 432 | calculated_chk -= buf[i] - 1; 433 | } 434 | 435 | if (calculated_chk != buf[HEADER_CHKSUM]) { 436 | printf("Cartridge header checksum invalid. This game will NOT boot on real hardware.\n"); 437 | } 438 | 439 | if (buf[HEADER_SGBFLAG] == 0x03 && buf[HEADER_OLDLICENSEE] != 0x33) { 440 | printf("SGB functions were enabled, but Old Licensee field is not set to 33h. This game will not be able to use SGB functions on real hardware.\n"); 441 | } 442 | 443 | if (opts.verbose) { 444 | switch (buf[HEADER_ROMSIZE]) { 445 | case 0: 446 | case 1: 447 | case 2: 448 | case 3: 449 | case 4: 450 | case 5: 451 | case 6: 452 | case 7: 453 | printf("%u KB ROM\n", 32 << buf[HEADER_ROMSIZE]); 454 | break; 455 | case 0x52: 456 | printf("1152 KB ROM\n"); 457 | break; 458 | case 0x53: 459 | printf("1280 KB ROM\n"); 460 | break; 461 | case 0x54: 462 | printf("1536 KB ROM\n"); 463 | break; 464 | default: 465 | printf("Unknown ROM size code\n"); 466 | break; 467 | } 468 | } 469 | } 470 | else if (opts.mode == MODE_LIST) { 471 | readRoms(opts.bank); 472 | listRoms(); 473 | } 474 | else if (opts.mode == MODE_ADD) { 475 | readRoms(opts.bank); 476 | addRom(opts.file); 477 | } 478 | else if (opts.mode == MODE_DELETE) { 479 | readRoms(opts.bank); 480 | deleteRom(opts.id); 481 | } 482 | 483 | // should never reach here 484 | else 485 | errx(1, "Unknown mode %d, file a bug report", opts.mode); 486 | 487 | // belt and suspenders 488 | free(buf); 489 | 490 | return 0; 491 | } 492 | 493 | int getRomSize(int sizeCode) { 494 | int size=0; 495 | switch (sizeCode) { 496 | case 0: 497 | case 1: 498 | case 2: 499 | case 3: 500 | case 4: 501 | case 5: 502 | case 6: 503 | case 7: 504 | size = (32 << sizeCode)*0x400; 505 | break; 506 | case 0x52: 507 | size = 1152*0x400; 508 | break; 509 | case 0x53: 510 | size = 1280*0x400; 511 | break; 512 | case 0x54: 513 | size = 1536*0x400; 514 | break; 515 | default: 516 | size = 128*0x400; 517 | printf("Unknown ROM size code\n"); 518 | break; 519 | } 520 | return size; 521 | } 522 | --------------------------------------------------------------------------------