mega-usb: download memory command (for custom OS)
[megadrive.git] / mega-usb / mega-usb.c
1 /*
2  * Tool for USB communication with Mega Everdrive
3  * Copyright (c) 2013,2014 notaz
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining
6  * a copy of this software and associated documentation files (the
7  * "Software"), to deal in the Software without restriction, including
8  * without limitation the rights to use, copy, modify, merge, publish,
9  * distribute, sublicense, and/or sell copies of the Software, and to
10  * permit persons to whom the Software is furnished to do so, subject to
11  * the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be 
14  * included in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25
26 #define _BSD_SOURCE
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <arpa/inet.h> // hton
35 #include <termios.h>
36 #include <unistd.h>
37
38 static int setup(int fd)
39 {
40         struct termios tty;
41         int ret;
42
43         memset(&tty, 0, sizeof(tty));
44
45         ret = tcgetattr(fd, &tty);
46         if (ret != 0)
47         {
48                 perror("tcgetattr");
49                 return 1;
50         }
51
52         tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
53                         | INLCR | IGNCR | ICRNL | IXON);
54         tty.c_oflag &= ~OPOST;
55         tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
56         tty.c_cflag &= ~(CSIZE | PARENB);
57         tty.c_cflag |= CS8;
58
59         //tty.c_cc[VMIN] = 1;
60         //tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout
61
62         ret = tcsetattr(fd, TCSANOW, &tty);
63         if (ret != 0) {
64                 perror("tcsetattr");
65                 return ret;
66         }
67
68         return 0;
69 }
70
71 static int write_to_cart(int fd, const void *data, size_t size)
72 {
73         int ret;
74
75         ret = write(fd, data, size);
76         if (ret != size) {
77                 fprintf(stderr, "write %d/%zd: ", ret, size);
78                 perror("");
79                 exit(1);
80                 //return -1;
81         }
82
83         return 0;
84 }
85
86 static int read_from_cart(int fd, void *data, size_t size)
87 {
88         size_t got = 0;
89         int ret;
90
91         while (got < size) {
92                 ret = read(fd, (char *)data + got, size - got);
93                 if (ret <= 0) {
94                         fprintf(stderr, "read %d %zd/%zd: ",
95                                 ret, got, size);
96                         perror("");
97                         exit(1);
98                         //return -1;
99                 }
100                 got += ret;
101         }
102
103         return 0;
104 }
105
106 #define send_cmd(fd, cmd) \
107         write_to_cart(fd, cmd, strlen(cmd))
108
109 static int read_check_byte(int fd, char expect)
110 {
111         char r = '0';
112         int ret;
113
114         ret = read_from_cart(fd, &r, 1);
115         if (ret != 0) {
116                 fprintf(stderr, "missing response, need '%c'\n", expect);
117                 return -1;
118         }
119
120         if (r != expect) {
121                 fprintf(stderr, "unexpected response: '%c', need '%c'\n",
122                         r, expect);
123                 return -1;
124         }
125
126         return 0;
127 }
128
129 static int write_with_check(int fd, const void *data, size_t size, char chk)
130 {
131         int ret;
132
133         ret = write_to_cart(fd, data, size);
134         if (ret)
135                 return ret;
136
137         ret = read_check_byte(fd, chk);
138         if (ret != 0) {
139                 if (size < 16)
140                         fprintf(stderr, "data sent: '%16s'\n",
141                                 (const char *)data);
142                 exit(1);
143                 //return -1;
144         }
145
146         return 0;
147 }
148
149 #define send_cmd_check_k(fd, cmd) \
150         write_with_check(fd, cmd, strlen(cmd), 'k')
151
152 static int send_file(int fd, const char *fname, const char *cmd)
153 {
154         char buf[0x10000];
155         int retval = -1;
156         FILE *f = NULL;
157         size_t blocksz;
158         size_t size;
159         int blocks;
160         int ret;
161         int i;
162
163         f = fopen(fname, "rb");
164         if (f == NULL) {
165                 fprintf(stderr, "fopen %s: ", fname);
166                 perror("");
167                 return -1;
168         }
169
170         fseek(f, 0, SEEK_END);
171         size = ftell(f);
172         fseek(f, 0, SEEK_SET);
173         if (size > 0xf00000) {
174                 fprintf(stderr, "size too large: %zd\n", size);
175                 goto out;
176         }
177
178         if (cmd[1] == 'f')
179                 blocksz = 1024;
180         else
181                 blocksz = sizeof(buf);
182
183         blocks = (size + blocksz - 1) / blocksz;
184
185         send_cmd(fd, cmd);
186         ret = write_with_check(fd, &blocks, 1, 'k');
187         if (ret)
188                 return ret;
189
190         // at this point, 'd' will arrive even if we don't
191         // write any more data, what's the point of that?
192         // probably a timeout?
193
194         for (i = 0; i < blocks; i++) {
195                 ret = fread(buf, 1, blocksz, f);
196                 if (i != blocks - 1 && ret != blocksz) {
197                         fprintf(stderr, "failed to read block %d/%d\n",
198                                 i, blocks);
199                 }
200                 if (ret < blocksz)
201                         memset(buf + ret, 0, blocksz - ret);
202
203                 write_to_cart(fd, buf, blocksz);
204         }
205
206         ret = read_check_byte(fd, 'd');
207         if (ret)
208                 goto out;
209
210         retval = 0;
211 out:
212         if (f != NULL)
213                 fclose(f);
214
215         return retval;
216 }
217
218 static int recv_file(int fd, const char *fname, unsigned int addr,
219         unsigned int size)
220 {
221         int retval = -1;
222         char *buf = NULL;
223         FILE *f = NULL;
224         struct {
225                 unsigned int addr;
226                 unsigned int size;
227         } addr_size;
228         int ret;
229
230         f = fopen(fname, "wb");
231         if (f == NULL) {
232                 fprintf(stderr, "fopen %s: ", fname);
233                 perror("");
234                 return -1;
235         }
236
237         buf = malloc(size);
238         if (buf == NULL) {
239                 fprintf(stderr, "OOM?\n");
240                 goto out;
241         }
242
243         send_cmd(fd, "*xd");
244
245         addr_size.addr = htonl(addr);
246         addr_size.size = htonl(size);
247         ret = write_with_check(fd, &addr_size, sizeof(addr_size), 'k');
248         if (ret)
249                 return ret;
250
251         ret = read_from_cart(fd, buf, size);
252         if (ret)
253                 goto out;
254
255         ret = fwrite(buf, 1, size, f);
256         if (ret != size) {
257                 fprintf(stderr, "fwrite %d/%d: ", ret, size);
258                 perror("");
259                 goto out;
260         }
261
262         retval = 0;
263 out:
264         if (f != NULL)
265                 fclose(f);
266         free(buf);
267
268         return retval;
269 }
270
271 static void usage(const char *argv0)
272 {
273         printf("usage:\n"
274                 "%s [-d <ttydevice>] [-f <file>] command(s)\n"
275                 "known commands for ED OS:\n"
276                 "  *g - upload game\n"
277                 "  *w - upload and start OS\n"
278                 "  *o - upload and start OS\n"
279                 "  *f - upload (and start?) FPGA firmware\n"
280                 "  *s - start last ROM (same as pressing start in menu)\n"
281                 "  *r<x> - run SDRAM contents as mapper x:\n"
282                 "    s - sms, m - md, o - OS app, c - cd BIOS, M - md10m\n"
283                 "upload commands must be followed by -f <file>\n"
284                 "custom OS commands:\n"
285                 "  *xd <addr> <size> <file> - download memory\n"
286                 , argv0);
287         exit(1);
288 }
289
290 static void invarg(int argc, char *argv[], int arg)
291 {
292         if (arg < argc)
293                 fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
294         else
295                 fprintf(stderr, "missing required argument %d\n", arg);
296         exit(1);
297 }
298
299 int main(int argc, char *argv[])
300 {
301         const char *portname = "/dev/ttyUSB0";
302         const char *pending_cmd = NULL;
303         int ret = -1;
304         int arg = 1;
305         int fd;
306
307         if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
308                 usage(argv[0]);
309
310         if (!strcmp(argv[arg], "-d")) {
311                 arg++;
312                 if (argv[arg] != NULL)
313                         portname = argv[arg];
314                 else
315                         invarg(argc, argv, arg);
316                 arg++;
317         }
318
319         fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
320         if (fd < 0) {
321                 fprintf(stderr, "open %s: ", portname);
322                 perror("");
323                 return 1;
324         }
325
326         setup(fd);
327
328         send_cmd_check_k(fd, "    *T");
329
330         for (; arg < argc; arg++) {
331                 if (!strcmp(argv[arg], "-f")) {
332                         arg++;
333                         if (argv[arg] == NULL)
334                                 invarg(argc, argv, argc);
335                         if (pending_cmd == NULL)
336                                 // assume game upload
337                                 pending_cmd = "*g";
338
339                         ret = send_file(fd, argv[arg], pending_cmd);
340                         if (ret)
341                                 return ret;
342
343                         pending_cmd = NULL;
344                         continue;
345                 }
346                 if (argv[arg + 1] && !strcmp(argv[arg + 1], "-f")) {
347                         /* we'll avoid sending command if there are
348                          * problems with specified file */
349                         pending_cmd = argv[arg];
350                         continue;
351                 }
352                 /* custom OS commands */
353                 if (!strcmp(argv[arg], "*xd")) {
354                         unsigned int addr, size;
355                         if (arg + 3 >= argc)
356                                 invarg(argc, argv, argc);
357                         addr = strtoul(argv[++arg], NULL, 0);
358                         size = strtoul(argv[++arg], NULL, 0);
359                         ret = recv_file(fd, argv[++arg], addr, size);
360                         if (ret)
361                                 return ret;
362
363                         continue;
364                 }
365
366                 /* passthrough */
367                 ret = send_cmd_check_k(fd, argv[arg]);
368         }
369
370         return ret;
371 }