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 */ |
41 | enum 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 | |
116 | static 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 | |
149 | static 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 | |
169 | static 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 | |
181 | static 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__) |
200 | static 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 | |
217 | static 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 |
226 | static void send_cmd(int fd, enum megapro_cmd cmd) |
227 | { |
228 | send_cmd_a(fd, cmd, NULL, 0); |
229 | } |
230 | #endif |
231 | |
232 | static 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 | |
246 | static 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 | |
261 | static 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 | |
269 | static 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) |
279 | static void fifo_write(int fd, const void *buf, size_t buf_size) |
280 | { |
281 | mem_write(fd, ADDR_FIFO, buf, buf_size); |
282 | } |
283 | |
284 | static void fifo_write_u32(int fd, uint32_t u32) |
285 | { |
286 | u32 = htonl(u32); |
287 | fifo_write(fd, &u32, sizeof(u32)); |
288 | } |
289 | |
290 | static void fifo_write_str(int fd, const char *s) |
291 | { |
292 | fifo_write(fd, s, strlen(s)); |
293 | } |
294 | |
295 | static 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__) |
306 | static 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 |
313 | static 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 | |
337 | static 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 | |
373 | static 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 |
406 | static 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 | |
428 | static 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 | |
440 | static 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 | |
449 | static int goodarg(const char *arg) |
450 | { |
451 | return arg && arg[0] && (arg[0] != '-' || arg[1] == 0); |
452 | } |
453 | |
454 | static 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 | |
461 | int 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 | } |