mega-usb-pro: new tool for Mega Everdrive Pro
[megadrive.git] / mega-usb-pro / mega-usb-pro.c
CommitLineData
5b7d7679 1/*
2 * Tool for USB communication with Mega Everdrive Pro
3 * Copyright (c) 2023 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#include <stdio.h>
27#include <string.h>
28#include <stdlib.h>
29#include <stdint.h>
30#include <assert.h>
31#include <errno.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <fcntl.h>
35#include <arpa/inet.h> // hton
36#include <termios.h>
37#include <poll.h>
38#include <unistd.h>
39
40/* from krikzz/MEGA-PRO/megalink/megalink/Edio.cs */
41enum megapro_cmd {
42 CMD_STATUS = 0x10,
43 CMD_GET_MODE = 0x11,
44 CMD_IO_RST = 0x12,
45 CMD_GET_VDC = 0x13,
46 CMD_RTC_GET = 0x14,
47 CMD_RTC_SET = 0x15,
48 CMD_FLA_RD = 0x16,
49 CMD_FLA_WR = 0x17,
50 CMD_FLA_WR_SDC = 0x18,
51 CMD_MEM_RD = 0x19,
52 CMD_MEM_WR = 0x1A,
53 CMD_MEM_SET = 0x1B,
54 CMD_MEM_TST = 0x1C,
55 CMD_MEM_CRC = 0x1D,
56 CMD_FPG_USB = 0x1E,
57 CMD_FPG_SDC = 0x1F,
58 CMD_FPG_FLA = 0x20,
59 CMD_FPG_CFG = 0x21,
60 CMD_USB_WR = 0x22,
61 CMD_FIFO_WR = 0x23,
62 CMD_UART_WR = 0x24,
63 CMD_REINIT = 0x25,
64 CMD_SYS_INF = 0x26,
65 CMD_GAME_CTR = 0x27,
66 CMD_UPD_EXEC = 0x28,
67 CMD_HOST_RST = 0x29,
68
69 CMD_DISK_INIT = 0xC0,
70 CMD_DISK_RD = 0xC1,
71 CMD_DISK_WR = 0xC2,
72 CMD_F_DIR_OPN = 0xC3,
73 CMD_F_DIR_RD = 0xC4,
74 CMD_F_DIR_LD = 0xC5,
75 CMD_F_DIR_SIZE = 0xC6,
76 CMD_F_DIR_PATH = 0xC7,
77 CMD_F_DIR_GET = 0xC8,
78 CMD_F_FOPN = 0xC9,
79 CMD_F_FRD = 0xCA,
80 CMD_F_FRD_MEM = 0xCB,
81 CMD_F_FWR = 0xCC,
82 CMD_F_FWR_MEM = 0xCD,
83 CMD_F_FCLOSE = 0xCE,
84 CMD_F_FPTR = 0xCF,
85 CMD_F_FINFO = 0xD0,
86 CMD_F_FCRC = 0xD1,
87 CMD_F_DIR_MK = 0xD2,
88 CMD_F_DEL = 0xD3,
89
90 CMD_USB_RECOV = 0xF0,
91 CMD_RUN_APP = 0xF1,
92};
93
94#define MAX_ROM_SIZE 0xF80000
95
96#define ADDR_ROM 0x0000000
97#define ADDR_SRAM 0x1000000
98#define ADDR_BRAM 0x1080000
99#define ADDR_CFG 0x1800000
100#define ADDR_SSR 0x1802000
101#define ADDR_FIFO 0x1810000
102
103#define SIZE_ROMX 0x1000000
104#define SIZE_SRAM 0x80000
105#define SIZE_BRAM 0x80000
106
107#define HOST_RST_OFF 0
108#define HOST_RST_SOFT 1
109#define HOST_RST_HARD 2
110
111#define fatal(fmt, ...) do { \
112 fprintf(stderr, "\n:%d " fmt, __LINE__, ##__VA_ARGS__); \
113 exit(1); \
114} while (0)
115
116static int serial_setup(int fd)
117{
118 struct termios tty;
119 int ret;
120
121 memset(&tty, 0, sizeof(tty));
122
123 ret = tcgetattr(fd, &tty);
124 if (ret != 0)
125 {
126 perror("tcgetattr");
127 return 1;
128 }
129
130 tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
131 | INLCR | IGNCR | ICRNL | IXON);
132 tty.c_oflag &= ~OPOST;
133 tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
134 tty.c_cflag &= ~(CSIZE | PARENB);
135 tty.c_cflag |= CS8;
136
137 //tty.c_cc[VMIN] = 1;
138 //tty.c_cc[VTIME] = 50; // seconds*10 read timeout?
139
140 ret = tcsetattr(fd, TCSANOW, &tty);
141 if (ret != 0) {
142 perror("tcsetattr");
143 return ret;
144 }
145
146 return 0;
147}
148
149static void flush_input(int fd)
150{
151 struct pollfd pfd = { fd, POLLIN, 0 };
152 uint8_t buf[0x10000];
153 int b = 0, total = 0;
154
155 while (poll(&pfd, 1, 10) > 0) {
156 b = read(fd, buf, sizeof(buf));
157 if (b > 0)
158 total += b;
159 }
160
161 if (total != 0) {
162 fprintf(stderr, "flushed %d leftover bytes", total);
163 if (b > 0)
164 fprintf(stderr, ", last '%c' %02x\n", buf[b-1], buf[b-1]);
165 puts("");
166 }
167}
168
169static void serial_write(int fd, const void *data, size_t size)
170{
171 int ret;
172
173 ret = write(fd, data, size);
174 if (ret != size) {
175 fprintf(stderr, "write %d/%zd: ", ret, size);
176 perror("");
177 exit(1);
178 }
179}
180
181static void serial_read(int fd, void *data, size_t size)
182{
183 size_t got = 0;
184 int ret;
185
186 while (got < size) {
187 ret = read(fd, (char *)data + got, size - got);
188 if (ret <= 0) {
189 fprintf(stderr, "read %d %zd/%zd: ",
190 ret, got, size);
191 perror("");
192 exit(1);
193 }
194 got += ret;
195 }
196}
197
198#define serial_read_expect_byte(fd, b) \
199 serial_read_expect_byte_(fd, b, __LINE__)
200static void serial_read_expect_byte_(int fd, char expected, int line)
201{
202 struct pollfd pfd = { fd, POLLIN, 0 };
203 char byte = 0;
204 int ret;
205
206 ret = poll(&pfd, 1, 5000); // 32x reset is > 3s
207 if (ret <= 0)
208 fatal(":%d poll %d,%d\n", line, ret, errno);
209
210 serial_read(fd, &byte, sizeof(byte));
211 if (byte != expected) {
212 fatal(":%d wrong response: '%c' %02x, expected '%c'\n",
213 line, byte, byte, expected);
214 }
215}
216
217static void send_cmd_a(int fd, enum megapro_cmd cmd, const void *arg, size_t alen)
218{
219 uint8_t buf[4] = { '+', '+' ^ 0xff, cmd, cmd ^ 0xff };
220 serial_write(fd, buf, sizeof(buf));
221 if (alen)
222 serial_write(fd, arg, alen);
223}
224
225#if 0
226static void send_cmd(int fd, enum megapro_cmd cmd)
227{
228 send_cmd_a(fd, cmd, NULL, 0);
229}
230#endif
231
232static void send_cmd_addr_len(int fd, enum megapro_cmd cmd, uint32_t addr, uint32_t len)
233{
234 struct {
235 uint32_t addr;
236 uint32_t len;
237 uint8_t exec; // ?
238 } __attribute__((packed)) args;
239 assert(sizeof(args) == 9);
240 args.addr = htonl(addr);
241 args.len = htonl(len);
242 args.exec = 0;
243 send_cmd_a(fd, cmd, &args, sizeof(args));
244}
245
246static void send_reset_cmd(int fd, uint8_t type)
247{
248 static int prev_type = -2;
249
250 if (type == prev_type)
251 return;
252
253 send_cmd_a(fd, CMD_HOST_RST, &type, sizeof(type));
254
255 //if (type == HOST_RST_OFF && prev_type != HOST_RST_OFF)
256 if (!(type & 1) && (prev_type & 1))
257 serial_read_expect_byte(fd, 'r');
258 prev_type = type;
259}
260
261static void mem_write(int fd, uint32_t addr, const void *buf, size_t buf_size)
262{
263 if (addr < ADDR_CFG)
264 send_reset_cmd(fd, HOST_RST_SOFT);
265 send_cmd_addr_len(fd, CMD_MEM_WR, addr, buf_size);
266 serial_write(fd, buf, buf_size);
267}
268
269static void mem_read(int fd, uint32_t addr, void *buf, size_t buf_size)
270{
271 if (addr < ADDR_CFG)
272 send_reset_cmd(fd, HOST_RST_SOFT);
273 send_cmd_addr_len(fd, CMD_MEM_RD, addr, buf_size);
274 serial_read(fd, buf, buf_size);
275}
276
277// supposedly fifo communicates with whatever is running on MD side?
278// (as there is no response to fifo stuff while commercial game runs)
279static void fifo_write(int fd, const void *buf, size_t buf_size)
280{
281 mem_write(fd, ADDR_FIFO, buf, buf_size);
282}
283
284static void fifo_write_u32(int fd, uint32_t u32)
285{
286 u32 = htonl(u32);
287 fifo_write(fd, &u32, sizeof(u32));
288}
289
290static void fifo_write_str(int fd, const char *s)
291{
292 fifo_write(fd, s, strlen(s));
293}
294
295static void fifo_write_len_n_str(int fd, const char *s)
296{
297 size_t len = strlen(s);
298 uint16_t len16 = len;
299 assert(len16 == len);
300 len16 = htons(len16);
301 fifo_write(fd, &len16, sizeof(len16));
302 fifo_write(fd, s, len);
303}
304
305#define run_t_check(fd) run_t_check_(fd, __LINE__)
306static void run_t_check_(int fd, int line)
307{
308 fifo_write_str(fd, "*t");
309 serial_read_expect_byte_(fd, 'k', line);
310}
311
312// try to recover from messed up states
313static void run_t_check_initial(int fd)
314{
315 struct pollfd pfd = { fd, POLLIN, 0 };
316 char byte = 0;
317 int ret;
318
319 fifo_write_str(fd, "*t");
320 ret = poll(&pfd, 1, 1000);
321 if (ret == 1) {
322 serial_read(fd, &byte, sizeof(byte));
323 if (byte == 'k')
324 return;
325 fprintf(stderr, "bad response to '*t': '%c' %02x\n", byte, byte);
326 }
327 else if (ret == 0)
328 fprintf(stderr, "timeout on initial '*t'\n");
329 else
330 fatal("poll %d,%d\n", ret, errno);
331
332 /*fprintf(stderr, "attempting reset...\n");
333 send_reset_cmd(fd, HOST_RST_OFF);
334 send_reset_cmd(fd, HOST_RST_SOFT);*/
335}
336
337static uint32_t send_from_file(int fd, uint32_t addr, uint32_t size_ovr,
338 const char *fname)
339{
340 uint8_t *buf = NULL;
341 FILE *f = NULL;
342 size_t size;
343 int ret;
344
345 f = fopen(fname, "rb");
346 if (f == NULL)
347 fatal("fopen %s: %s\n", fname, strerror(errno));
348
349 fseek(f, 0, SEEK_END);
350 size = ftell(f);
351 fseek(f, 0, SEEK_SET);
352 if (size_ovr) {
353 if (size_ovr > size)
354 fatal("size override %u > %zd\n", size_ovr, size);
355 size = size_ovr;
356 }
357 if (size > MAX_ROM_SIZE)
358 fatal("size too large: %zd\n", size);
359 buf = malloc(size);
360 if (!buf)
361 fatal("oom\n");
362 ret = fread(buf, 1, size, f);
363 if (ret != size)
364 fatal("fread %s: %d/%zd %s\n", fname, ret, size, strerror(errno));
365
366 mem_write(fd, addr, buf, size);
367
368 free(buf);
369 fclose(f);
370 return size;
371}
372
373static uint32_t recv_to_file(int fd, uint32_t addr, uint32_t size,
374 const char *fname)
375{
376 uint8_t *buf = NULL;
377 FILE *f = NULL;
378 int ret;
379
380 if (strcmp(fname, "-") == 0)
381 f = stdout;
382 else
383 f = fopen(fname, "wb");
384 if (f == NULL)
385 fatal("fopen %s: %s", fname, strerror(errno));
386
387 buf = malloc(size);
388 if (!buf)
389 fatal("oom\n");
390
391 mem_read(fd, addr, buf, size);
392
393 ret = fwrite(buf, 1, size, f);
394 if (ret != size)
395 fatal("fwrite %s: %d/%d %s\n", fname, ret, size, strerror(errno));
396
397 free(buf);
398 if (f == stdout)
399 fflush(stdout);
400 else
401 fclose(f);
402 return size;
403}
404
405// reference: megalink/Usbio::loadGame
406static void send_run_game(int fd, uint32_t size_ovr, const char *fname)
407{
408 const char *fname_out;
409 char name_out[256];
410 uint32_t size;
411
412 fname_out = strrchr(fname, '/');
413 fname_out = fname_out ? fname_out + 1 : fname;
414 snprintf(name_out, sizeof(name_out), "USB:%s", fname_out);
415
416 size = send_from_file(fd, 0, size_ovr, fname);
417 send_reset_cmd(fd, HOST_RST_OFF);
418
419 run_t_check(fd);
420
421 fifo_write_str(fd, "*g");
422 fifo_write_u32(fd, size);
423 fifo_write_len_n_str(fd, name_out);
424
425 serial_read_expect_byte(fd, 0); // not done by reference
426}
427
428static void usage(const char *argv0)
429{
430 fprintf(stderr, "usage:\n"
431 "%s [-d <ttydevice>] [opts]\n"
432 " -rst [type] reset\n"
433 " -l <file> [len] load and run\n"
434 " -w <file> <addr> [len] write to cart\n"
435 " -r <file> <addr> <len> read from cart\n"
436 , argv0);
437 exit(1);
438}
439
440static void invarg(int argc, char *argv[], int arg)
441{
442 if (arg < argc)
443 fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
444 else
445 fprintf(stderr, "missing required argument %d\n", arg);
446 exit(1);
447}
448
449static int goodarg(const char *arg)
450{
451 return arg && arg[0] && (arg[0] != '-' || arg[1] == 0);
452}
453
454static void *getarg(int argc, char *argv[], int arg)
455{
456 if (!goodarg(argv[arg]))
457 invarg(argc, argv, arg);
458 return argv[arg];
459}
460
461int main(int argc, char *argv[])
462{
463 const char *portname = "/dev/ttyACM0";
464 const char *fname;
465 int no_exit_t_check = 0;
466 uint8_t byte;
467 int arg = 1;
468 int fd;
469
470 if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
471 usage(argv[0]);
472
473 if (!strcmp(argv[arg], "-d")) {
474 arg++;
475 if (argv[arg] != NULL)
476 portname = argv[arg];
477 else
478 invarg(argc, argv, arg);
479 arg++;
480 }
481
482 fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
483 if (fd < 0) {
484 fprintf(stderr, "open %s: ", portname);
485 perror("");
486 return 1;
487 }
488
489 serial_setup(fd);
490 flush_input(fd);
491 run_t_check_initial(fd);
492
493 for (; arg < argc; arg++)
494 {
495 uint32_t addr, size = 0;
496 if (!strcmp(argv[arg], "-rst")) {
497 byte = HOST_RST_SOFT;
498 if (goodarg(argv[arg + 1]))
499 byte = strtol(argv[++arg], NULL, 0);
500 send_reset_cmd(fd, byte);
501 }
502 else if (!strcmp(argv[arg], "-l")) {
503 fname = getarg(argc, argv, ++arg);
504 if (goodarg(argv[arg + 1]))
505 size = strtol(argv[++arg], NULL, 0);
506 send_run_game(fd, size, fname);
507 no_exit_t_check = 1; // disturbs game startup
508 }
509 else if (!strcmp(argv[arg], "-w")) {
510 fname = getarg(argc, argv, ++arg);
511 addr = strtol(getarg(argc, argv, ++arg), NULL, 0);
512 if (goodarg(argv[arg + 1]))
513 size = strtol(argv[++arg], NULL, 0);
514 send_from_file(fd, addr, size, fname);
515 }
516 else if (!strcmp(argv[arg], "-r")) {
517 fname = getarg(argc, argv, ++arg);
518 addr = strtol(getarg(argc, argv, ++arg), NULL, 0);
519 size = strtol(getarg(argc, argv, ++arg), NULL, 0);
520 recv_to_file(fd, addr, size, fname);
521 }
522 }
523 send_reset_cmd(fd, HOST_RST_OFF);
524 if (!no_exit_t_check)
525 run_t_check(fd);
526
527 return 0;
528}