+ f = fopen(fname, "rb");
+ if (f == NULL) {
+ fprintf(stderr, "fopen %s: ", fname);
+ perror("");
+ return -1;
+ }
+
+ fseek(f, 0, SEEK_END);
+ size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ if (size > 0xf00000) {
+ fprintf(stderr, "size too large: %zd\n", size);
+ goto out;
+ }
+
+ if (cmd[1] == 'f')
+ blocksz = 1024;
+ else
+ blocksz = sizeof(buf);
+
+ blocks = (size + blocksz - 1) / blocksz;
+ blocks_b = blocks;
+
+ send_cmd(fd, cmd);
+ ret = write_with_check(fd, &blocks_b, 1, 'k');
+ if (ret)
+ return ret;
+
+ // at this point, 'd' will arrive even if we don't
+ // write any more data, what's the point of that?
+ // probably a timeout?
+
+ for (i = 0; i < blocks; i++) {
+ ret = fread(buf, 1, blocksz, f);
+ if (i != blocks - 1 && ret != blocksz) {
+ fprintf(stderr, "failed to read block %d/%d\n",
+ i, blocks);
+ }
+ if (ret < blocksz)
+ memset(buf + ret, 0, blocksz - ret);
+
+ write_to_cart(fd, buf, blocksz);
+ }
+
+ ret = read_check_byte(fd, 'd');
+ if (ret)
+ goto out;
+
+ retval = 0;
+out:
+ if (f != NULL)
+ fclose(f);
+
+ return retval;
+}
+
+static int recv_file(int fd, const char *fname, unsigned int addr,
+ unsigned int size)
+{
+ int retval = -1;
+ char *buf = NULL;
+ FILE *f = NULL;
+ struct {
+ unsigned int addr;
+ unsigned int size;
+ } addr_size;
+ int ret;
+
+ f = fopen(fname, "wb");
+ if (f == NULL) {
+ fprintf(stderr, "fopen %s: ", fname);
+ perror("");
+ return -1;
+ }
+
+ buf = malloc(size);
+ if (buf == NULL) {
+ fprintf(stderr, "OOM?\n");
+ goto out;
+ }
+
+ send_cmd(fd, "*xd");
+
+ addr_size.addr = htonl(addr);
+ addr_size.size = htonl(size);
+ ret = write_with_check(fd, &addr_size, sizeof(addr_size), 'k');
+ if (ret)
+ return ret;
+
+ ret = read_from_cart(fd, buf, size);
+ if (ret)
+ goto out;
+
+ ret = fwrite(buf, 1, size, f);
+ if (ret != size) {
+ fprintf(stderr, "fwrite %d/%d: ", ret, size);
+ perror("");
+ goto out;
+ }
+
+ retval = 0;
+out:
+ if (f != NULL)
+ fclose(f);
+ free(buf);
+
+ return retval;
+}
+
+static void usage(const char *argv0)
+{
+ printf("usage:\n"
+ "%s [-d <ttydevice>] [-f <file>] command(s)\n"
+ "known commands for ED OS:\n"
+ " *g - upload game\n"
+ " *w - upload and start OS\n"
+ " *o - upload and start OS\n"
+ " *f - upload (and start?) FPGA firmware\n"
+ " *s - start last ROM (same as pressing start in menu)\n"
+ " *r<x> - run SDRAM contents as mapper x:\n"
+ " s - sms, m - md, o - OS app, c - cd BIOS, M - md10m\n"
+ "upload commands must be followed by -f <file>\n"
+ "custom OS commands:\n"
+ " *xd <addr> <size> <file> - download memory\n"
+ , argv0);
+ exit(1);
+}
+
+static void invarg(int argc, char *argv[], int arg)
+{
+ if (arg < argc)
+ fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
+ else
+ fprintf(stderr, "missing required argument %d\n", arg);
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *portname = "/dev/ttyUSB0";
+ const char *pending_cmd = NULL;
+ int ret = -1;
+ int arg = 1;
+ int fd;
+
+ if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
+ usage(argv[0]);
+
+ if (!strcmp(argv[arg], "-d")) {
+ arg++;
+ if (argv[arg] != NULL)
+ portname = argv[arg];
+ else
+ invarg(argc, argv, arg);
+ arg++;
+ }
+