OXYGENHUB
◆ main enhanced-x16-emulator / src / main.c
72.4 KiB · C · last changed feat: the RRDC control port (-controlport), contract 0.6: GET/POST /perf from the Performance panel's measurements, built by perf_json.c with DAP's x16/perfStats; a guest marker region named in advance; a framed guest's regions carried across the frame boundary and an interrupt after $F1 counted as work (#1) 25 days ago source blame history raw
9120e874 Andy McCall 1
// Commander X16 Emulator
2
// Copyright (c) 2019,2022 Michael Steil
3
// All rights reserved. License: 2-clause BSD
4
5
// Feature-test macros for glibc-style libcs only. macOS hides BSD extensions
6
// this code uses when they are set, and MSVC's CRT ignores them entirely.
7
#if !defined(__APPLE__) && !defined(_MSC_VER)
8
#define _XOPEN_SOURCE   600
9
#define _POSIX_C_SOURCE 1
10
#endif
11
#include <stdlib.h>
12
#include <stdbool.h>
13
#include <stdint.h>
14
#include <stdio.h>
15
#include <string.h>
16
#include "compat.h"
17
#include <limits.h>
18
#ifdef __MINGW32__
19
#include <ctype.h>
20
#endif
21
#include "cpu/fake6502.h"
22
#include "timing.h"
23
#include "disasm.h"
24
#include "files.h"
25
#include "memory.h"
26
#include "video.h"
27
#include "via.h"
28
#include "serial.h"
29
#include "i2c.h"
30
#include "rtc.h"
31
#include "smc.h"
32
#include "vera_spi.h"
33
#include "sdcard.h"
34
#include "ieee.h"
35
#include "glue.h"
36
#include "debugger.h"
37
#include "dbg_info.h"
38
#include "dbg_load.h"
39
#include "debug_server.h"
40
#include "source_view.h"
41
#include "code_map.h"
42
#include "perf_budget.h"
2980b7b6 Andy McCall ↶ 43
#include "retro_control.h"   // the RRDC control port (-controlport)
9120e874 Andy McCall 44
#include "vera_bandwidth.h"
45
#include "cpu/irq_ctx.h"
46
#include "utf8.h"
47
#include "iso_8859_15.h"
48
#include "joystick.h"
49
#include "rom_symbols.h"
50
#include "ymglue.h"
51
#include "audio.h"
52
#include "version.h"
53
#include "wav_recorder.h"
54
#include "testbench.h"
55
#include "cartridge.h"
56
#include "midi.h"
57
#include "git_rev.h"
58
59
#ifdef __EMSCRIPTEN__
60
#include <emscripten.h>
61
#include <pthread.h>
62
#endif
63
64
void *emulator_loop(void *param);
65
void emscripten_main_loop(void);
66
67
// This must match the KERNAL's set!
68
char *keymaps[] = {
69
	"en-us",
70
	"en-us-int",
71
	"en-gb",
72
	"sv",
73
	"de",
74
	"da",
75
	"it",
76
	"pl",
77
	"nb",
78
	"hu",
79
	"es",
80
	"fi",
81
	"pt-br",
82
	"cz",
83
	"jp",
84
	"fr",
85
	"de-ch",
86
	"en-us-dvo",
87
	"et",
88
	"fr-be",
89
	"fr-ca",
90
	"is",
91
	"pt",
92
	"hr",
93
	"sk",
94
	"sl",
95
	"lv",
96
	"lt",
97
};
98
99
#ifdef PERFSTAT
100
uint32_t stat[65536];
101
#endif
102
103
bool debugger_enabled = false;
104
// -imgui: the graphical debugger window. Independent of debugger_enabled, so
105
// the two UIs can be used together or separately. Always defined, even in a
106
// build without the UI, so the emulator loop needs no #ifdef.
107
bool imgui_debugger_enabled = false;
2980b7b6 Andy McCall ↶ 108
// The Retro Remote Debug Controller's HTTP port (-controlport), 0 = off.
109
// control_backend.c maps it onto the machine; extern/retro-remote-debug-
110
// controller serves it.
111
int control_port = 0;
112
int x16_control_start(int port);
113
extern bool control_port_active;
9120e874 Andy McCall 114
// True when a local interactive debugger was asked for (-debug/-bp/-wp), as
115
// distinct from the machinery merely being switched on. A DAP client uses this
116
// to decide whether anyone is left to resume the machine if it disconnects.
117
bool debug_window_enabled = false;
118
char *paste_text = NULL;
119
char *clipboard_buffer = NULL;
120
char paste_text_data[65536];
121
bool pasting_bas = false;
122
123
uint16_t num_banks = 1;
124
uint16_t num_ram_banks = 64; // 512 KB default
125
126
bool log_video = false;
127
bool log_speed = false;
128
bool log_keyboard = false;
129
bool dump_cpu = false;
130
bool dump_ram = true;
131
bool dump_bank = true;
132
bool dump_vram = false;
133
bool warp_mode = false;
134
bool warp_pastes = false;
135
bool grab_mouse = false;
136
echo_mode_t echo_mode;
137
bool save_on_exit = true;
138
bool disable_emu_cmd_keys = false;
139
bool set_system_time = false;
140
bool has_serial = false;
141
bool no_ieee_intercept = false;
142
bool has_via2 = false;
143
gif_recorder_state_t record_gif = RECORD_GIF_DISABLED;
144
char *gif_path = NULL;
145
char *wav_path = NULL;
146
uint8_t *fsroot_path = NULL;
147
uint8_t *startin_path = NULL;
148
uint8_t keymap = 0; // KERNAL's default
149
int window_scale = 1;
150
float screen_x_scale = 1.0;
151
float window_opacity = 1.0;
152
char *scale_quality = "best";
153
bool test_init_complete=false;
154
bool headless = false;
155
bool fullscreen = false;
156
bool testbench = false;
157
bool enable_midline = false;
158
bool ym2151_irq_support = false;
159
char *cartridge_path = NULL;
160
161
bool has_midi_card = false;
162
uint16_t midi_card_addr;
163
164
bool using_hostfs = true;
165
166
// Per-instruction clock reference. File-scope so emulator_step_during_move()
167
// (run from the Windows modal move/resize loop) advances the peripheral clocks
168
// in lock-step with emulator_loop() and neither double-counts the other's ticks.
169
static uint32_t old_clockticks6502 = 0;
170
171
uint8_t MHZ = 8;
172
173
#ifdef TRACE
174
bool trace_mode = false;
175
uint16_t trace_address = 0;
176
#endif
177
178
int instruction_counter;
179
SDL_RWops *prg_file;
180
bool prg_finished_loading;
181
int prg_override_start = -1;
182
bool run_after_load = false;
183
184
char *nvram_path = NULL;
185
186
bool pwr_long_press=false;
187
bool is_gen2 = false;
188
189
#ifdef TRACE
190
#include "rom_labels.h"
191
#include "rom_lst.h"
192
char *
193
label_for_address(uint16_t address)
194
{
195
	uint16_t *addresses;
196
	char **labels;
197
	int count;
198
	switch (memory_get_rom_bank()) {
199
		case 0:
200
			addresses = addresses_bank0;
201
			labels = labels_bank0;
202
			count = sizeof(addresses_bank0) / sizeof(uint16_t);
203
			break;
204
		case 1:
205
			addresses = addresses_bank1;
206
			labels = labels_bank1;
207
			count = sizeof(addresses_bank1) / sizeof(uint16_t);
208
			break;
209
		case 2:
210
			addresses = addresses_bank2;
211
			labels = labels_bank2;
212
			count = sizeof(addresses_bank2) / sizeof(uint16_t);
213
			break;
214
		case 3:
215
			addresses = addresses_bank3;
216
			labels = labels_bank3;
217
			count = sizeof(addresses_bank3) / sizeof(uint16_t);
218
			break;
219
		case 4:
220
			addresses = addresses_bank4;
221
			labels = labels_bank4;
222
			count = sizeof(addresses_bank4) / sizeof(uint16_t);
223
			break;
224
		case 5:
225
			addresses = addresses_bank5;
226
			labels = labels_bank5;
227
			count = sizeof(addresses_bank5) / sizeof(uint16_t);
228
			break;
229
		case 6:
230
			addresses = addresses_bank6;
231
			labels = labels_bank6;
232
			count = sizeof(addresses_bank6) / sizeof(uint16_t);
233
			break;
234
		case 10:
235
			addresses = addresses_bankA;
236
			labels = labels_bankA;
237
			count = sizeof(addresses_bankA) / sizeof(uint16_t);
238
			break;
239
		case 11:
240
			addresses = addresses_bankB;
241
			labels = labels_bankB;
242
			count = sizeof(addresses_bankB) / sizeof(uint16_t);
243
			break;
244
		case 12:
245
			addresses = addresses_bankC;
246
			labels = labels_bankC;
247
			count = sizeof(addresses_bankC) / sizeof(uint16_t);
248
			break;
249
		case 13:
250
			addresses = addresses_bankD;
251
			labels = labels_bankD;
252
			count = sizeof(addresses_bankD) / sizeof(uint16_t);
253
			break;
254
		case 14:
255
			addresses = addresses_bankE;
256
			labels = labels_bankE;
257
			count = sizeof(addresses_bankE) / sizeof(uint16_t);
258
			break;
259
		case 15:
260
			addresses = addresses_bankF;
261
			labels = labels_bankF;
262
			count = sizeof(addresses_bankF) / sizeof(uint16_t);
263
			break;
264
		default:
265
			addresses = NULL;
266
			labels = NULL;
267
	}
268
269
	if (!addresses) {
270
		return NULL;
271
	}
272
273
	for (int i = 0; i < count; i++) {
274
		if (address == addresses[i]) {
275
			return labels[i];
276
		}
277
	}
278
	return NULL;
279
}
280
281
char *
282
lst_for_address(uint16_t address)
283
{
284
	if (address < 0xc000) {
285
		return NULL;
286
	}
287
288
	char **lst;
289
	switch (memory_get_rom_bank()) {
290
		case 0: lst = lst_bank0; break;
291
		case 2: lst = lst_bank2; break;
292
		case 3: lst = lst_bank3; break;
293
		case 4: lst = lst_bank4; break;
294
		case 5: lst = lst_bank5; break;
295
		case 7: lst = lst_bank7; break;
296
		case 8: lst = lst_bank8; break;
297
		case 9: lst = lst_bank9; break;
298
		case 10: lst = lst_bankA; break;
299
		case 11: lst = lst_bankB; break;
300
		case 12: lst = lst_bankC; break;
301
		case 13: lst = lst_bankD; break;
302
		case 14: lst = lst_bankE; break;
303
		case 15: lst = lst_bankF; break;
304
		default:
305
			return NULL;
306
	}
307
	return lst[address - 0xc000];
308
}
309
#endif
310
311
void
312
machine_dump(const char* reason)
313
{
314
	printf("Dumping system memory. Reason: %s\n", reason);
315
	int index = 0;
316
	char filename[22];
317
	for (;;) {
318
		if (!index) {
319
			strcpy(filename, "dump.bin");
320
		} else {
321
			sprintf(filename, "dump-%i.bin", index);
322
		}
323
		if (access(filename, F_OK) == -1) {
324
			break;
325
		}
326
		index++;
327
	}
328
	SDL_RWops *f = SDL_RWFromFile(filename, "wb");
329
	if (!f) {
330
		printf("Cannot write to %s!\n", filename);
331
		return;
332
	}
333
334
	if (dump_cpu) {
335
		SDL_RWwrite(f, &regs.a, sizeof(uint8_t), 1);
336
		SDL_RWwrite(f, &regs.xl, sizeof(uint8_t), 1);
337
		SDL_RWwrite(f, &regs.yl, sizeof(uint8_t), 1);
338
		SDL_RWwrite(f, &regs.sp, sizeof(uint8_t), 1);
339
		SDL_RWwrite(f, &regs.status, sizeof(uint8_t), 1);
340
		SDL_RWwrite(f, &regs.pc, sizeof(uint16_t), 1);
341
	}
342
	memory_save(f, dump_ram, dump_bank);
343
344
	if (dump_vram) {
345
		video_save(f);
346
	}
347
348
	SDL_RWclose(f);
349
	printf("Dumped system to %s.\n", filename);
350
}
351
352
void mouse_state_init(void)
353
{
354
	kernal_mouse_enabled = 0;
355
	SDL_ShowCursor((mouse_grabbed || kernal_mouse_enabled) ? SDL_DISABLE : SDL_ENABLE);
356
}
357
358
void
359
machine_reset()
360
{
361
	i2c_reset_state();
362
	ieee_init();
363
	memory_reset();
364
	vera_spi_init();
365
	via1_init();
366
	if (has_via2) {
367
		via2_init();
368
	}
369
	video_reset();
370
	mouse_state_init();
371
	reset6502(regs.is65c816);
372
	midi_serial_init();
373
	code_map_reset();  // discard stale disassembly anchors so post-reset code re-aligns
374
	dbg_load_reset();  // and any load that was mid-flight
375
}
376
377
void
378
machine_nmi()
379
{
380
	nmi6502();
381
}
382
383
void
384
machine_paste(char *s, bool handle_free)
385
{
386
	if (s) {
387
		paste_text = s;
388
		if (handle_free) {
389
			clipboard_buffer = s; // so that we can free this later
390
		}
391
		pasting_bas = true;
392
		if (warp_pastes) machine_set_warp(true);
393
	}
394
}
395
396
// Every warp change re-bases the clock. Leaving warp with a surplus already
397
// accrued makes the throttle sleep it off, which at a slow target is seconds of
398
// frozen machine; entering warp with a deficit makes it sprint. The paste paths
399
// used to set the flag directly and did both.
400
void
401
machine_set_warp(bool on)
402
{
403
	if (warp_mode == on) {
404
		return;
405
	}
406
	warp_mode = on;
407
	timing_init();
408
}
409
410
void
411
machine_toggle_warp()
412
{
413
	machine_set_warp(!warp_mode);
414
}
415
416
417
static bool
418
is_kernal()
419
{
420
	// only for KERNAL
421
	return (debug_read6502(0xfff6, 0, USE_CURRENT_X16_BANK) == 'M' &&
422
			debug_read6502(0xfff7, 0, USE_CURRENT_X16_BANK) == 'I' &&
423
			debug_read6502(0xfff8, 0, USE_CURRENT_X16_BANK) == 'S' &&
424
			debug_read6502(0xfff9, 0, USE_CURRENT_X16_BANK) == 'T')
425
		|| (debug_read6502(0xc008, 0, USE_CURRENT_X16_BANK) == 'M' &&
426
			debug_read6502(0xc009, 0, USE_CURRENT_X16_BANK) == 'I' &&
427
			debug_read6502(0xc00a, 0, USE_CURRENT_X16_BANK) == 'S' &&
428
			debug_read6502(0xc00b, 0, USE_CURRENT_X16_BANK) == 'T');
429
}
430
431
// A breakpoint or watch address as written on the command line: a plain
432
// address, or a banked one as bb:aaaa. The bare bbaaaa form upstream accepted
433
// for -debug still works.
434
//
435
// An address written on its own carries no bank, so it applies to whichever
436
// bank is mapped -- writing "A000" says nothing about banks, and should not
437
// silently mean bank 0. Write "05:A000" to mean one particular bank.
438
// Returns false (and complains) rather than silently arming address 0 for
439
// input that is not a valid address, which is what a bare strtol() would do
440
// with a typo.
441
static bool
442
parse_breakpoint_arg(const char *arg, struct breakpoint *out)
443
{
444
	char         *end = NULL;
445
	unsigned long first = strtoul(arg, &end, 16);
446
447
	if (end == arg) {
448
		fprintf(stderr, "Not a hex address: %s\n", arg);
449
		return false;
450
	}
451
452
	if (*end == ':') {
453
		const char   *addr_str = end + 1;
454
		char         *end2     = NULL;
455
		unsigned long addr     = strtoul(addr_str, &end2, 16);
456
		if (end2 == addr_str || *end2 != '\0' || addr > 0xFFFF) {
457
			fprintf(stderr, "Not a valid banked address (expected bb:aaaa): %s\n", arg);
458
			return false;
459
		}
460
		if (first > 0xFF) {
461
			fprintf(stderr, "Bank out of range (00-FF): %s\n", arg);
462
			return false;
463
		}
464
		if (addr < 0xA000) {
465
			fprintf(stderr, "$%04lX is not in a banked window, so a bank is meaningless: %s\n",
466
			        addr, arg);
467
			return false;
468
		}
469
		out->pc      = (int)addr;
470
		out->bank    = 0;
471
		out->x16Bank = (int)first;
472
		return true;
473
	}
474
475
	if (*end != '\0') {
476
		fprintf(stderr, "Not a hex address: %s\n", arg);
477
		return false;
478
	}
479
480
	if (first > 0xFFFFFF) {
481
		fprintf(stderr, "Address out of range: %s\n", arg);
482
		return false;
483
	}
484
485
	out->bank = 0;
486
	if (first > 0xFFFF) {
487
		out->pc      = (int)(first & 0xFFFF);
488
		out->x16Bank = (int)(first >> 16);
489
	} else {
490
		out->pc      = (int)first;
491
		out->x16Bank = DEBUG_BANK_ANY;   // no bank given: whichever is mapped
492
	}
493
	return true;
494
}
495
496
static void
497
usage()
498
{
499
	printf("\nCommander X16 Emulator r%s (%s)", VER, VER_NAME);
500
#ifdef GIT_REV
501
	printf(", "GIT_REV);
502
#endif
503
	printf("\n(C)2019, 2023 Michael Steil et al.\n");
504
	printf("All rights reserved. License: 2-clause BSD\n\n");
505
	printf("Usage: x16emu [option] ...\n\n");
506
	printf("-rom <rom.bin>\n");
507
	printf("\tOverride KERNAL/BASIC/* ROM file.\n");
508
	printf("-ram <ramsize>\n");
509
	printf("\tSpecify banked RAM size in KB (8, 16, 32, ..., 2048).\n");
510
	printf("\tThe default is 512.\n");
511
	printf("-nvram <nvram.bin>\n");
512
	printf("\tSpecify NVRAM image. By default, the machine starts with\n");
513
	printf("\tempty NVRAM and does not save it to disk.\n");
514
	printf("-keymap <keymap>\n");
515
	printf("\tEnable a specific keyboard layout decode table.\n");
516
	printf("-sdcard <sdcard.img>\n");
517
	printf("\tSpecify SD card image (partition map + FAT32)\n");
518
	printf("-cart <crtfile.crt>\n");
519
	printf("\tLoads a specially-formatted cartridge file.\n");
520
	printf("-cartbin <romfile.bin>\n");
521
	printf("\tLoads a raw cartridge file starting at ROM bank 32. After\n");
522
	printf("\tloading, all of the affected banks will function as RAM.\n");
523
	printf("-serial\n");
524
	printf("\tConnect host fs through Serial Bus [experimental]\n");
525
	printf("-nohostieee / -nohostfs\n");
526
	printf("\tDisable HostFS through IEEE API interception.\n");
527
	printf("\tIEEE API HostFS is normally enabled unless -sdcard or\n");
528
	printf("\t-serial is specified.\n");
529
	printf("-hostfsdev <unit>\n");
530
	printf("\tSet the HostFS IEEE device number. Range 8-31. Default: %d.\n", ieee_unit);
531
	printf("-fsroot <directory>\n");
532
	printf("\tSpecify the host filesystem directory path which is to\n");
533
	printf("\tact as the emulated root directory of the Commander X16.\n");
534
	printf("\tDefault is the current working directory.\n");
535
	printf("-startin <directory>\n");
536
	printf("\tSpecify the host filesystem directory path that the\n");
537
	printf("\temulated filesystem starts in. Default is the current\n");
538
	printf("\tworking directory if it lies within the hierarchy of fsroot,\n");
539
	printf("\totherwise it defaults to fsroot itself.\n");
540
	printf("-noemucmdkeys\n");
541
	printf("\tDisable emulator command keys.\n");
542
	printf("-capture\n");
543
	printf("\tStart emulator with mouse/keyboard captured.\n");
544
	printf("-nokeyboardcapture\n");
545
	printf("\tWhile in capture mode, causes the emulator not to intercept\n");
546
	printf("\tkeyboard combinations which are used by the operating system,\n");
547
	printf("\tsuch as Alt+Tab.\n");
548
	printf("-prg <app.prg>[,<load_addr>]\n");
549
	printf("\tLoad application from the *host filesystem* into RAM,\n");
550
	printf("\teven if an SD card is attached.\n");
551
	printf("\tThe override load address is hex without a prefix.\n");
552
	printf("-bas <app.txt>\n");
553
	printf("\tInject a BASIC program in ASCII encoding through the\n");
554
	printf("\tkeyboard.\n");
555
	printf("-run\n");
556
	printf("\tStart the -prg/-bas program using RUN\n");
557
	printf("-warp\n");
558
	printf("\tEnable warp mode, run emulator as fast as possible.\n");
559
	printf("-pastewarp\n");
560
	printf("\tEnable warp mode during pastes and during loading via -bas.\n");
561
	printf("-echo [{iso|raw}]\n");
562
	printf("\tPrint all KERNAL output to the host's stdout.\n");
563
	printf("\tBy default, everything but printable ASCII characters get\n");
564
	printf("\tescaped. \"iso\" will escape everything but non-printable\n");
565
	printf("\tISO-8859-15 characters and convert the output to UTF-8.\n");
566
	printf("\t\"raw\" will not do any substitutions.\n");
567
	printf("\tWith the BASIC statement \"LIST\", this can be used\n");
568
	printf("\tto detokenize a BASIC program.\n");
569
	printf("-log {K|S|V}...\n");
570
	printf("\tEnable logging of (K)eyboard, (S)peed, (V)ideo.\n");
571
	printf("\tMultiple characters are possible, e.g. -log KS\n");
572
	printf("-gif <file.gif>[,wait]\n");
573
	printf("\tRecord a gif for the video output.\n");
574
	printf("\tUse ,wait to start paused.\n");
575
	printf("\tPOKE $9FB5,2 to start recording.\n");
576
	printf("\tPOKE $9FB5,1 to capture a single frame.\n");
577
	printf("\tPOKE $9FB5,0 to pause.\n");
578
	printf("-wav <file.wav>[{,wait|,auto}]\n");
579
	printf("\tRecord a wav for the audio output.\n");
580
	printf("\tUse ,wait to start paused, or ,auto to start paused and automatically begin recording on the first non-zero audio signal.\n");
581
	printf("\tPOKE $9FB6,2 to automatically begin recording on the first non-zero audio signal.\n");
582
	printf("\tPOKE $9FB6,1 to begin recording immediately.\n");
583
	printf("\tPOKE $9FB6,0 to pause.\n");
584
	printf("-scale {1|2|3|4}\n");
585
	printf("\tScale output to an integer multiple of 640x480\n");
586
	printf("-quality {nearest|linear|best}\n");
587
	printf("\tScaling algorithm quality\n");
588
	printf("-widescreen\n");
589
	printf("\tStretch output to 16:9 resolution to mimic display of a widescreen monitor.\n");
590
	printf("-fullscreen\n");
591
	printf("\tStart up in fullscreen mode instead of in a window.\n");
592
	printf("-opacity (0.0,...,1.0)\n");
593
	printf("\tSet the opacity value (0.0 for transparent, 1.0 for opaque) of the window. (default: %.1f)\n", window_opacity);
594
	printf("-debug [<address>]\n");
595
	printf("\tEnable debugger. Optionally, set a breakpoint\n");
596
#ifdef HAS_IMGUI
597
	printf("-imgui\n");
598
	printf("\tOpen the graphical debugger in a second window: memory,\n");
599
	printf("\tdisassembly, CPU, VERA, source, breakpoints, symbols and call\n");
600
	printf("\tstack. Independent of -debug, and can be combined with it.\n");
601
#endif
602
	printf("-bp <address>\n");
603
	printf("\tArm a breakpoint before the first instruction runs. This is the\n");
604
	printf("\tonly way to stop inside something that is over before you could\n");
605
	printf("\tattach: start-up code, KERNAL or ROM routines, a program that runs\n");
606
	printf("\tfrom its load address. Unlike -debug's optional address it can be\n");
607
	printf("\trepeated. Usually paired with -imgui, so the machine is already\n");
608
	printf("\tstopped where you wanted it when the window appears.\n");
609
	printf("\tA breakpoint needs a debugger to resume from, so this opens -debug\n");
610
	printf("\tunless -imgui or -debugport is already providing one.\n");
611
	printf("\tAn address on its own applies whatever bank is mapped. To pin one\n");
612
	printf("\tbank of the $A000-$FFFF windows, write bb:aaaa, e.g. 05:A000 for\n");
613
	printf("\t$A000 in RAM bank 5. Below $A000 nothing is banked, so no bank may\n");
614
	printf("\tbe given.\n");
615
	printf("-wp <address>[,<length>]\n");
616
	printf("\tWatch memory: stop when the program writes to this address, or\n");
617
	printf("\tanywhere in <length> bytes from it. Takes the same address forms as\n");
618
	printf("\t-bp, can be repeated, and picks a debugger the same way.\n");
619
	printf("-dbgauto / -no-dbgauto\n");
620
	printf("\tPick up debug info for programs the running machine loads, not\n");
621
	printf("\tjust the one named by -dbgfile: when it LOADs a host file, read\n");
622
	printf("\tthe .dbg sitting beside it. On whenever the debugger is enabled.\n");
623
	printf("\tUse -no-dbgauto to suppress it: the file read is chosen by the\n");
624
	printf("\temulated program, and a .dbg can name source paths anywhere on\n");
625
	printf("\tthe host, so untrusted software is better run without it.\n");
626
	printf("-dbgfile <path>\n");
627
	printf("\tLoad a cc65 .dbg file, so addresses can be mapped back to source\n");
628
	printf("\tfiles and line numbers. Combine with -srcpath if the sources are not\n");
629
	printf("\tbeside the .dbg file.\n");
630
	printf("-srcpath <dir>\n");
631
	printf("\tAdd a directory to search for the source files a .dbg file names.\n");
632
	printf("\tCan be repeated.\n");
2980b7b6 Andy McCall ↶ 633
	printf("-controlport <port>\n");
634
	printf("\tServe the Retro Remote Debug Controller's HTTP contract on\n");
635
	printf("\t127.0.0.1:<port>, so a test harness can read memory, step frames,\n");
636
	printf("\tinject input and read the performance metrics (GET /perf).\n");
9120e874 Andy McCall 637
	printf("-debugport [port]\n");
638
	printf("\tServe the Debug Adapter Protocol on <port> (default %d), so an\n",
639
	       DEBUG_SERVER_DEFAULT_PORT);
640
	printf("\teditor can set breakpoints and step through source. The emulator\n");
641
	printf("\tkeeps running until a client sets a breakpoint that hits.\n");
642
	printf("-randram\n");
643
	printf("\t(deprecated, no effect)\n");
644
	printf("-zeroram\n");
645
	printf("\tSet all RAM to zero instead of uninitialized random values\n");
646
	printf("-wuninit\n");
647
	printf("\tPrints warning to stdout if uninitialized RAM is accessed\n");
648
	printf("-memorystats <file.txt>\n");
649
	printf("\tSaves memory access statistics to the given file when emulator exits\n");
650
	printf("-dump {C|R|B|V}...\n");
651
	printf("\tConfigure system dump: (C)PU, (R)AM, (B)anked-RAM, (V)RAM\n");
652
	printf("\tMultiple characters are possible, e.g. -dump CV ; Default: RB\n");
653
	printf("-joy1\n");
654
	printf("\tEnable binding a gamepad to SNES controller port 1\n");
655
	printf("-joy2\n");
656
	printf("\tEnable binding a gamepad to SNES controller port 2\n");
657
	printf("-joy3\n");
658
	printf("\tEnable binding a gamepad to SNES controller port 3\n");
659
	printf("-joy4\n");
660
	printf("\tEnable binding a gamepad to SNES controller port 4\n");
661
	printf("-sound <output device>\n");
662
	printf("\tSet the output device used for audio emulation\n");
663
	printf("\tIf output device is 'none', no audio is generated\n");
664
	printf("-abufs <number of audio buffers>\n");
665
	printf("\tSet the number of audio buffers used for playback.\n");
666
	printf("\tIf using HostFS, the default is 32, otherwise 8.\n");
667
	printf("\tIncreasing this will reduce stutter on slower computers,\n");
668
	printf("\tbut will increase audio latency.\n");
669
	printf("-rtc\n");
670
	printf("\tSet the real-time-clock to the current system time and date.\n");
671
	printf("-via2\n");
672
	printf("\tInstall the second VIA chip expansion at $9F10\n");
673
	printf("-testbench\n");
674
	printf("\tHeadless mode for unit testing with an external test runner\n");
675
	printf("-mhz <integer>\n");
676
	printf("\tRun the emulator with a system clock speed other than the default of\n");
677
	printf("\t8 MHz. Valid values are in the range of 1-40, inclusive. This option\n");
678
	printf("\tis meant mainly for benchmarking, and may not reflect accurate\n");
679
	printf("\thardware behavior.\n");
680
	printf("-midline-effects\n");
681
	printf("\tApproximate mid-line raster effects when changing tile, sprite,\n");
682
	printf("\tand palette data. Requires a fast host CPU.\n");
683
	printf("-enable-ym2151-irq\n");
684
	printf("\tConnect the YM2151 IRQ source to the emulated CPU. This option increases\n");
685
	printf("\tCPU usage as audio render is triggered for every CPU instruction.\n");
686
	printf("-c02\n");
687
	printf("\tRun the emulator under an emulated 65C02 (default)\n");
688
	printf("-c816\n");
689
	printf("\tRun the emulator under an emulated 65C816\n");
690
	printf("\tThis option is experimental.\n");
691
	printf("-rockwell\n");
692
	printf("\tSuppress warning emitted when encountering a Rockwell extension on the 65C02\n");
693
	printf("-longpwron\n");
694
	printf("\tSimulate a long press of the power button at system power-on.\n");
695
#ifdef HAS_FLUIDSYNTH
696
	printf("-midicard [<address>]\n");
697
	printf("\tInstall a serial MIDI card at the specified address, or at $9F60 by default.\n");
698
	printf("\tThe -sf2 option must be specified along with this option.\n");
699
	printf("-sf2 <SoundFont filename>\n");
700
	printf("\tInitialize MIDI synth with the specified SoundFont.\n");
701
	printf("\tThe -midicard option must be specified along with this option.\n");
702
	printf("-midi-in\n");
703
	printf("\tConnect the system MIDI input devices to the input of the first UART\n");
704
	printf("\tof the emulated MIDI card. The -midicard option is required for this\n");
705
	printf("\toption to have any effect.\n");
706
#endif
707
#ifdef TRACE
708
	printf("-trace [<address>]\n");
709
	printf("\tPrint instruction trace. Optionally, a trigger address\n");
710
	printf("\tcan be specified.\n");
711
#endif
712
	printf("-version\n");
713
	printf("\tPrint additional version information of the emulator and ROM.\n");
714
	printf("\n");
715
	exit(1);
716
}
717
718
void
719
usage_keymap()
720
{
721
	printf("The following keymaps are supported:\n");
722
	for (int i = 0; i < sizeof(keymaps)/sizeof(*keymaps); i++) {
723
		printf("\t%s\n", keymaps[i]);
724
	}
725
	exit(1);
726
}
727
728
void no_fluidsynth_warning(void)
729
{
730
	static bool already_warned;
731
732
	if (!already_warned) {
733
		fprintf(stderr, "\nWarning: x16emu was built without FluidSynth support,\n");
734
		fprintf(stderr, "so the MIDI synth will be inoperative.\n\n");
735
#if defined(__linux__)
736
		fprintf(stderr, "To build x16emu with fluidsynth support, you distro may\n");
737
		fprintf(stderr, "have a libfluidsynth-dev or fluidsynth-devel package that\n");
738
		fprintf(stderr, "needs to be installed before building x16emu.\n\n");
739
#elif defined(__APPLE__)
740
		fprintf(stderr, "To build x16emu with fluidsynth support,\n");
741
		fprintf(stderr, "install the homebrew package fluid-synth before\n");
742
		fprintf(stderr, "building x16emu.\n\n");
743
#elif defined(_WIN64)
744
		fprintf(stderr, "To build x16emu with fluidsynth support under MSYS2,\n");
745
		fprintf(stderr, "install the mingw-w64-x86_64-fluidsynth package before\n");
746
		fprintf(stderr, "building x16emu.\n\n");
747
#elif defined(_WIN32)
748
		fprintf(stderr, "To build x16emu with fluidsynth support under MSYS2,\n");
749
		fprintf(stderr, "install the mingw-w64-i686-fluidsynth package before\n");
750
		fprintf(stderr, "building x16emu.\n\n");
751
#endif
752
		fprintf(stderr, "Then rebuild x16emu from scratch.\n");
753
		already_warned = true;
754
	}
755
}
756
757
int
758
main(int argc, char **argv)
759
{
760
	char *rom_filename = "rom.bin";
761
	char rom_path_data[PATH_MAX];
762
763
	char *rom_path = rom_path_data;
764
	char *prg_path = NULL;
765
	char *bas_path = NULL;
766
	char *sf2_path = NULL;
767
	char *sdcard_path = NULL;
768
	bool run_test = false;
769
	int test_number = 0;
770
	int audio_buffers = 8;
771
	bool zeroram = false;
772
	bool audio_buffers_set = false;
773
	bool hostfs_set = false;
774
775
	const char *audio_dev_name = NULL;
776
777
	run_after_load = false;
778
779
	char *base_path = SDL_GetBasePath();
780
781
	// This causes the emulator to load ROM data from the executable's directory when
782
	// no ROM file is specified on the command line.
783
	memcpy(rom_path, base_path, strlen(base_path) + 1);
784
	strncpy(rom_path + strlen(rom_path), rom_filename, PATH_MAX - strlen(rom_path));
785
	memory_randomize_ram(true);
786
787
	argc--;
788
	argv++;
789
790
	// Unset until an explicit flag says otherwise, so the default below can
791
	// follow the debugger rather than the order of the arguments.
792
	int dbg_auto_opt = -1;
793
794
	// Whether -bp or -wp asked for something to be armed before the machine
795
	// starts. Which front end ends up driving it is decided after parsing, so
796
	// the answer does not depend on the order the flags were written in.
797
	bool cli_stops_requested = false;
798
799
	while (argc > 0) {
800
		if (!strcmp(argv[0], "-rom")) {
801
			argc--;
802
			argv++;
803
			if (!argc || argv[0][0] == '-') {
804
				usage();
805
			}
806
			rom_path = argv[0];
807
			argc--;
808
			argv++;
809
		} else if (!strcmp(argv[0], "-ram")) {
810
			argc--;
811
			argv++;
812
			if (!argc || argv[0][0] == '-') {
813
				usage();
814
			}
815
			int kb = atoi(argv[0]);
816
			if (!((kb & 7)==0) || kb < 8 || kb > 2048) {
817
				printf("-ram value must be a multiple of 8 in the range of 8-2048.\n");
818
				exit(1);
819
			}
820
			num_ram_banks = kb /8;
821
			argc--;
822
			argv++;
823
		} else if (!strcmp(argv[0], "-keymap")) {
824
			argc--;
825
			argv++;
826
			if (!argc || argv[0][0] == '-') {
827
				usage_keymap();
828
			}
829
			bool found = false;
830
			for (int i = 0; i < sizeof(keymaps)/sizeof(*keymaps); i++) {
831
				if (!strcmp(argv[0], keymaps[i])) {
832
					found = true;
833
					keymap = i;
834
				}
835
			}
836
			if (!found) {
837
				usage_keymap();
838
			}
839
			argc--;
840
			argv++;
841
		} else if (!strcmp(argv[0], "-prg")) {
842
			argc--;
843
			argv++;
844
			if (!argc || argv[0][0] == '-') {
845
				usage();
846
			}
847
			prg_path = argv[0];
848
			argc--;
849
			argv++;
850
		} else if (!strcmp(argv[0], "-midicard")) {
851
#ifndef HAS_FLUIDSYNTH
852
			no_fluidsynth_warning();
853
#endif
854
			argc--;
855
			argv++;
856
			has_midi_card = true;
857
			if (argc && argv[0][0] != '-') {
858
				midi_card_addr = 0x9f00 | ((uint16_t)strtol(argv[0], NULL, 16) & 0xff);
859
				midi_card_addr &= 0xfff0;
860
				argc--;
861
				argv++;
862
			} else {
863
				midi_card_addr = 0x9f60;
864
			}
865
		} else if (!strcmp(argv[0], "-sf2")) {
866
#ifndef HAS_FLUIDSYNTH
867
			no_fluidsynth_warning();
868
#endif
869
			argc--;
870
			argv++;
871
			if (!argc || argv[0][0] == '-') {
872
				usage();
873
			}
874
			sf2_path = argv[0];
875
			argc--;
876
			argv++;
877
		} else if (!strcmp(argv[0], "-midi-in")) {
878
#ifndef HAS_FLUIDSYNTH
879
			no_fluidsynth_warning();
880
#endif
881
			argc--;
882
			argv++;
883
			fs_midi_in_connect = true;
884
		} else if (!strcmp(argv[0], "-run")) {
885
			argc--;
886
			argv++;
887
			run_after_load = true;
888
		} else if (!strcmp(argv[0], "-bas")) {
889
			argc--;
890
			argv++;
891
			if (!argc || argv[0][0] == '-') {
892
				usage();
893
			}
894
			bas_path = argv[0];
895
			argc--;
896
			argv++;
897
		} else if (!strcmp(argv[0], "-test")) {
898
			argc--;
899
			argv++;
900
			if (!argc || argv[0][0] == '-') {
901
				usage();
902
			}
903
			test_number = atoi(argv[0]);
904
			run_test = true;
905
			argc--;
906
			argv++;
907
		} else if (!strcmp(argv[0], "-nvram")) {
908
			argc--;
909
			argv++;
910
			if (!argc || argv[0][0] == '-') {
911
				usage();
912
			}
913
			nvram_path = argv[0];
914
			argc--;
915
			argv++;
916
		} else if (!strcmp(argv[0], "-sdcard")) {
917
			argc--;
918
			argv++;
919
			if (!argc || argv[0][0] == '-') {
920
				usage();
921
			}
922
			sdcard_path = argv[0];
923
			argc--;
924
			argv++;
925
		} else if (!strcmp(argv[0], "-cart")) {
926
			argc--;
927
			argv++;
928
			if (!argc || argv[0][0] == '-') {
929
				usage();
930
			}
931
			cartridge_path = argv[0];
932
			argc--;
933
			argv++;
934
		} else if (!strcmp(argv[0], "-cartbin")) {
935
			argc--;
936
			argv++;
937
			if (!argc || argv[0][0] == '-') {
938
				usage();
939
			}
940
			cartridge_new();
941
			cartridge_define_bank_range(32, 255, CART_BANK_UNINITIALIZED_RAM);
942
			cartridge_import_files(argv, 1, 32, CART_BANK_INITIALIZED_RAM, 0);
943
			argc--;
944
			argv++;
945
		} else if (!strcmp(argv[0], "-warp")) {
946
			argc--;
947
			argv++;
948
			warp_mode = true;
949
		} else if (!strcmp(argv[0], "-pastewarp")) {
950
			argc--;
951
			argv++;
952
			warp_pastes = true;
953
		} else if (!strcmp(argv[0], "-echo")) {
954
			argc--;
955
			argv++;
956
			if (argc && argv[0][0] != '-') {
957
				if (!strcmp(argv[0], "raw")) {
958
					echo_mode = ECHO_MODE_RAW;
959
				} else if (!strcmp(argv[0], "iso")) {
960
						echo_mode = ECHO_MODE_ISO;
961
				} else {
962
					usage();
963
				}
964
				argc--;
965
				argv++;
966
			} else {
967
				echo_mode = ECHO_MODE_COOKED;
968
			}
969
		} else if (!strcmp(argv[0], "-log")) {
970
			argc--;
971
			argv++;
972
			if (!argc || argv[0][0] == '-') {
973
				usage();
974
			}
975
			for (char *p = argv[0]; *p; p++) {
976
				switch (tolower(*p)) {
977
					case 'k':
978
						log_keyboard = true;
979
						break;
980
					case 's':
981
						log_speed = true;
982
						break;
983
					case 'v':
984
						log_video = true;
985
						break;
986
					default:
987
						usage();
988
				}
989
			}
990
			argc--;
991
			argv++;
992
		} else if (!strcmp(argv[0], "-dump")) {
993
			argc--;
994
			argv++;
995
			if (!argc || argv[0][0] == '-') {
996
				usage();
997
			}
998
			dump_cpu = false;
999
			dump_ram = false;
1000
			dump_bank = false;
1001
			dump_vram = false;
1002
			for (char *p = argv[0]; *p; p++) {
1003
				switch (tolower(*p)) {
1004
					case 'c':
1005
						dump_cpu = true;
1006
						break;
1007
					case 'r':
1008
						dump_ram = true;
1009
						break;
1010
					case 'b':
1011
						dump_bank = true;
1012
						break;
1013
					case 'v':
1014
						dump_vram = true;
1015
						break;
1016
					default:
1017
						usage();
1018
				}
1019
			}
1020
			argc--;
1021
			argv++;
1022
		} else if (!strcmp(argv[0], "-gif")) {
1023
			argc--;
1024
			argv++;
1025
			// set up for recording
1026
			record_gif = RECORD_GIF_PAUSED;
1027
			if (!argc || argv[0][0] == '-') {
1028
				usage();
1029
			}
1030
			gif_path = argv[0];
1031
			argv++;
1032
			argc--;
1033
		} else if (!strcmp(argv[0], "-wav")) {
1034
			argc--;
1035
			argv++;
1036
			// set up for recording
1037
			if (!argc || argv[0][0] == '-') {
1038
				usage();
1039
			}
1040
			wav_path = argv[0];
1041
			argv++;
1042
			argc--;
1043
		} else if (!strcmp(argv[0], "-debug")) {
1044
			argc--;
1045
			argv++;
1046
			debugger_enabled = true;
1047
			debug_window_enabled = true;
1048
			if (argc && argv[0][0] != '-') {
1049
				// Adds rather than replaces, so it composes with -bp instead of
1050
				// depending on which came first on the command line.
1051
				struct breakpoint bp = { 0 };
1052
				if (!parse_breakpoint_arg(argv[0], &bp)) {
1053
					usage();
1054
				}
1055
				if (debug_bp_add_for(bp, DEBUG_OWNER_CLI) == DEBUG_ADD_FULL) {
1056
					fprintf(stderr, "Out of memory adding breakpoint: %s\n", argv[0]);
1057
					usage();
1058
				}
1059
				argc--;
1060
				argv++;
1061
			}
1062
		} else if (!strcmp(argv[0], "-imgui")) {
1063
			argc--;
1064
			argv++;
1065
#ifdef HAS_IMGUI
1066
			imgui_debugger_enabled = true;
1067
#else
1068
			// Fail loudly rather than starting with a flag that silently does
1069
			// nothing: this build was configured with -DENABLE_IMGUI=OFF.
1070
			printf("This build has no graphical debugger; -imgui is unavailable.\n");
1071
			exit(1);
1072
#endif
1073
		} else if (!strcmp(argv[0], "-bp")) {
1074
			argc--;
1075
			argv++;
1076
			debugger_enabled = true;
1077
			cli_stops_requested = true;
1078
			if (!argc || argv[0][0] == '-') {
1079
				usage();
1080
			}
1081
			{
1082
				struct breakpoint bp = { 0 };
1083
				if (!parse_breakpoint_arg(argv[0], &bp)) {
1084
					usage();
1085
				}
1086
				if (debug_bp_add_for(bp, DEBUG_OWNER_CLI) == DEBUG_ADD_FULL) {
1087
					fprintf(stderr, "Out of memory adding breakpoint: %s\n", argv[0]);
1088
					usage();
1089
				}
1090
			}
1091
			argc--;
1092
			argv++;
1093
		} else if (!strcmp(argv[0], "-wp")) {
1094
			argc--;
1095
			argv++;
1096
			debugger_enabled = true;
1097
			cli_stops_requested = true;
1098
			if (!argc || argv[0][0] == '-') {
1099
				usage();
1100
			}
1101
			{
1102
				// address[,length] -- the length is optional and defaults to a
1103
				// single byte.
1104
				char spec[64];
1105
				if (strlen(argv[0]) >= sizeof(spec)) {
1106
					// Truncating would parse a different address than the one
1107
					// that was typed, and say nothing about it.
1108
					fprintf(stderr, "Not a valid watch spec: %s\n", argv[0]);
1109
					usage();
1110
				}
1111
				strcpy(spec, argv[0]);
1112
1113
				unsigned long len   = 1;
1114
				char         *comma = strchr(spec, ',');
1115
				if (comma) {
1116
					*comma = '\0';
1117
					char *lend = NULL;
1118
					len = strtoul(comma + 1, &lend, 0);
1119
					if (lend == comma + 1 || *lend != '\0' || len == 0 || len > 0xFFFF) {
1120
						fprintf(stderr, "Not a valid watch length (1-65535): %s\n", comma + 1);
1121
						usage();
1122
					}
1123
				}
1124
1125
				struct breakpoint wp = { 0 };
1126
				if (!parse_breakpoint_arg(spec, &wp)) {
1127
					usage();
1128
				}
1129
				// A duplicate and a full table used to be one answer, so the
1130
				// message had to guess at both. They are distinct now.
1131
				if (debug_wp_add_for((uint16_t)wp.pc, (uint16_t)len, wp.x16Bank,
1132
				                     DEBUG_OWNER_CLI) == DEBUG_ADD_FULL) {
1133
					fprintf(stderr, "Could not add watchpoint (no more than %d): %s\n",
1134
					        MAX_WATCHPOINTS, argv[0]);
1135
					usage();
1136
				}
1137
			}
1138
			argc--;
1139
			argv++;
1140
		} else if (!strcmp(argv[0], "-dbgauto")) {
1141
			argc--;
1142
			argv++;
1143
			dbg_auto_opt = 1;
1144
		} else if (!strcmp(argv[0], "-no-dbgauto")) {
1145
			argc--;
1146
			argv++;
1147
			dbg_auto_opt = 0;
2980b7b6 Andy McCall ↶ 1148
		} else if (!strcmp(argv[0], "-controlport")) {
1149
			argc--;
1150
			argv++;
1151
			if (!argc || argv[0][0] == '-') {
1152
				fprintf(stderr, "-controlport requires a port number\n");
1153
				return 1;
1154
			}
1155
			control_port = (int)strtol(argv[0], NULL, 0);
1156
			argc--;
1157
			argv++;
9120e874 Andy McCall 1158
		} else if (!strcmp(argv[0], "-debugport")) {
1159
			argc--;
1160
			argv++;
1161
			// The breakpoint machinery lives behind debugger_enabled, and a DAP
1162
			// client needs it whether or not the SDL debug window is up. It is
1163
			// deliberately NOT debug_window_enabled: there is no local UI here,
1164
			// which is what lets the server resume the machine when its client
1165
			// disconnects rather than leaving it halted with nobody to drive it.
1166
			debugger_enabled = true;
1167
			int port = DEBUG_SERVER_DEFAULT_PORT;
1168
			if (argc && argv[0][0] != '-') {
1169
				port = atoi(argv[0]);
1170
				argc--;
1171
				argv++;
1172
			}
1173
#ifdef HAS_DAP
1174
			// The CPU keeps running; a client attaches when it is ready, and
1175
			// breakpoints it sets are what stop execution.
1176
			if (debug_server_init(port) != 0) {
1177
				fprintf(stderr, "Failed to start debug server on port %d\n", port);
1178
				return 1;
1179
			}
1180
#else
1181
			(void)port;
1182
			fprintf(stderr, "-debugport requires a build with the DAP server "
1183
			                "(cJSON was not available at configure time)\n");
1184
			return 1;
1185
#endif
1186
		} else if (!strcmp(argv[0], "-dbgfile")) {
1187
			argc--;
1188
			argv++;
1189
			if (!argc || argv[0][0] == '-') {
1190
				fprintf(stderr, "-dbgfile requires a file path\n");
1191
				return 1;
1192
			}
1193
			if (dbg_info_load(argv[0]) != 0) {
1194
				fprintf(stderr, "Warning: failed to load debug info from '%s'\n", argv[0]);
1195
			} else {
1196
				// Make the .dbg's own directory a source-search root.
1197
				source_view_add_path(dbg_info_get_dbg_dir());
1198
			}
1199
			argc--;
1200
			argv++;
1201
		} else if (!strcmp(argv[0], "-srcpath")) {
1202
			argc--;
1203
			argv++;
1204
			if (!argc || argv[0][0] == '-') {
1205
				fprintf(stderr, "-srcpath requires a directory path\n");
1206
				return 1;
1207
			}
1208
			source_view_add_path(argv[0]);
1209
			argc--;
1210
			argv++;
1211
		} else if (!strcmp(argv[0], "-randram")) {
1212
			/* this operation has no effect anymore, randomizing the Ram is now default */
1213
			argc--;
1214
			argv++;
1215
		} else if (!strcmp(argv[0], "-zeroram")) {
1216
			argc--;
1217
			argv++;
1218
			memory_randomize_ram(false);
1219
			zeroram = true;
1220
		} else if (!strcmp(argv[0], "-wuninit")) {
1221
			argc--;
1222
			argv++;
1223
			memory_report_uninitialized_access(true);
1224
		} else if (!strcmp(argv[0], "-memorystats")) {
1225
			argc--;
1226
			argv++;
1227
			if (!argc || argv[0][0] == '-') {
1228
				usage();
1229
			}
1230
			memory_report_usage_statistics(argv[0]);
1231
			argv++;
1232
			argc--;
1233
		} else if (!strcmp(argv[0], "-joy1")) {
1234
			argc--;
1235
			argv++;
1236
			Joystick_slots_enabled[0] = true;
1237
		} else if (!strcmp(argv[0], "-joy2")){
1238
			argc--;
1239
			argv++;
1240
			Joystick_slots_enabled[1] = true;
1241
		} else if (!strcmp(argv[0], "-joy3")) {
1242
			argc--;
1243
			argv++;
1244
			Joystick_slots_enabled[2] = true;
1245
		} else if (!strcmp(argv[0], "-joy4")) {
1246
			argc--;
1247
			argv++;
1248
			Joystick_slots_enabled[3] = true;
1249
#ifdef TRACE
1250
		} else if (!strcmp(argv[0], "-trace")) {
1251
			argc--;
1252
			argv++;
1253
			if (argc && argv[0][0] != '-') {
1254
				trace_mode = false;
1255
				trace_address = (uint16_t)strtol(argv[0], NULL, 16);
1256
				argc--;
1257
				argv++;
1258
			} else {
1259
				trace_mode = true;
1260
				trace_address = 0;
1261
			}
1262
#endif
1263
		} else if (!strcmp(argv[0], "-scale")) {
1264
			argc--;
1265
			argv++;
1266
			if(!argc || argv[0][0] == '-') {
1267
				usage();
1268
			}
1269
			for(char *p = argv[0]; *p; p++) {
1270
				switch(tolower(*p)) {
1271
				case '1':
1272
					window_scale = 1;
1273
					break;
1274
				case '2':
1275
					window_scale = 2;
1276
					break;
1277
				case '3':
1278
					window_scale = 3;
1279
					break;
1280
				case '4':
1281
					window_scale = 4;
1282
					break;
1283
				default:
1284
					usage();
1285
				}
1286
			}
1287
			argc--;
1288
			argv++;
1289
		} else if (!strcmp(argv[0], "-quality")) {
1290
			argc--;
1291
			argv++;
1292
			if(!argc || argv[0][0] == '-') {
1293
				usage();
1294
			}
1295
			if (!strcmp(argv[0], "nearest") ||
1296
				!strcmp(argv[0], "linear") ||
1297
				!strcmp(argv[0], "best")) {
1298
				scale_quality = argv[0];
1299
			} else {
1300
				usage();
1301
			}
1302
			argc--;
1303
			argv++;
1304
		} else if (!strcmp(argv[0], "-widescreen")) {
1305
			argc--;
1306
			argv++;
1307
			screen_x_scale = 4.0f/3.0f;
1308
		} else if (!strcmp(argv[0], "-fullscreen")) {
1309
			argc--;
1310
			argv++;
1311
			fullscreen = true;
1312
		} else if (!strcmp(argv[0], "-opacity")) {
1313
			argc--;
1314
			argv++;
1315
			if (!argc || argv[0][0] == '-') {
1316
				usage();
1317
			}
1318
			window_opacity = strtof(argv[0], NULL);
1319
			argc--;
1320
			argv++;
1321
		} else if (!strcmp(argv[0], "-sound")) {
1322
			argc--;
1323
			argv++;
1324
			if (!argc || argv[0][0] == '-') {
1325
				audio_usage();
1326
			}
1327
			audio_dev_name = argv[0];
1328
			argc--;
1329
			argv++;
1330
		} else if (!strcmp(argv[0], "-abufs")) {
1331
			argc--;
1332
			argv++;
1333
			if (!argc || argv[0][0] == '-') {
1334
				usage();
1335
			}
1336
			audio_buffers = (int)strtol(argv[0], NULL, 10);
1337
			audio_buffers_set = true;
1338
			argc--;
1339
			argv++;
1340
		} else if (!strcmp(argv[0], "-rtc")) {
1341
			argc--;
1342
			argv++;
1343
			set_system_time = true;
1344
		} else if (!strcmp(argv[0], "-serial")) {
1345
			argc--;
1346
			argv++;
1347
			has_serial = true;
1348
		} else if (!strcmp(argv[0], "-nohostieee") || !strcmp(argv[0], "-nohostfs")) {
1349
			argc--;
1350
			argv++;
1351
			no_ieee_intercept = true;
1352
			hostfs_set = false;
1353
			using_hostfs = false;
1354
		} else if (!strcmp(argv[0], "-hostfsdev")){
1355
			argc--;
1356
			argv++;
1357
			if (!argc || argv[0][0] == '-') {
1358
				usage();
1359
			}
1360
			ieee_unit = (uint8_t)strtol(argv[0], NULL, 10);
1361
			if (ieee_unit < 8 || ieee_unit > 31) {
1362
				usage();
1363
			}
1364
			hostfs_set = true;
1365
			using_hostfs = true;
1366
			argc--;
1367
			argv++;
1368
		} else if (!strcmp(argv[0], "-fsroot")) {
1369
			argc--;
1370
			argv++;
1371
			if (!argc || argv[0][0] == '-') {
1372
				usage();
1373
			}
1374
			fsroot_path = (uint8_t *)argv[0];
1375
			argc--;
1376
			argv++;
1377
		} else if (!strcmp(argv[0], "-startin")) {
1378
			argc--;
1379
			argv++;
1380
			if (!argc || argv[0][0] == '-') {
1381
				usage();
1382
			}
1383
			startin_path = (uint8_t *)argv[0];
1384
			argc--;
1385
			argv++;
1386
		} else if (!strcmp(argv[0], "-noemucmdkeys")) {
1387
			argc--;
1388
			argv++;
1389
			disable_emu_cmd_keys = true;
1390
		} else if (!strcmp(argv[0], "-capture")) {
1391
			argc--;
1392
			argv++;
1393
			grab_mouse = true;
1394
		} else if (!strcmp(argv[0], "-longpwron")) {
1395
			argc--;
1396
			argv++;
1397
			pwr_long_press = true;
1398
		} else if (!strcmp(argv[0], "-nokeyboardcapture")) {
1399
			argc--;
1400
			argv++;
1401
			no_keyboard_capture = true;
1402
		} else if (!strcmp(argv[0], "-via2")) {
1403
			argc--;
1404
			argv++;
1405
			has_via2 = true;
1406
		} else if (!strcmp(argv[0], "-version")){
1407
			printf("%s", VER_INFO);
1408
#ifdef GIT_REV
1409
			printf(" "GIT_REV"\n");
1410
#else
1411
			printf("\n");
1412
#endif
1413
			argc--;
1414
			argv++;
1415
			exit(0);
1416
		} else if (!strcmp(argv[0], "-testbench")){
1417
			printf("Testbench mode...\n");
1418
			fflush(stdout);
1419
			argc--;
1420
			argv++;
1421
			testbench=true;
1422
			headless=true;
1423
		} else if (!strcmp(argv[0], "-mhz")){
1424
			argc--;
1425
			argv++;
1426
			if (!argc || argv[0][0] == '-') {
1427
				usage();
1428
			}
1429
			MHZ = (uint8_t)strtol(argv[0], NULL, 10);
1430
			if (MHZ < 1 || MHZ > 40) {
1431
				usage();
1432
			}
1433
			argc--;
1434
			argv++;
1435
		} else if (!strcmp(argv[0], "-midline-effects")){
1436
			argc--;
1437
			argv++;
1438
			enable_midline = true;
1439
		} else if (!strcmp(argv[0], "-enable-ym2151-irq")){
1440
			argc--;
1441
			argv++;
1442
			ym2151_irq_support = true;
1443
		} else if (!strcmp(argv[0], "-c816")){
1444
			argc--;
1445
			argv++;
1446
			regs.is65c816 = true;
1447
			is_gen2 = false;
1448
		} else if (!strcmp(argv[0], "-c02")){
1449
			argc--;
1450
			argv++;
1451
			regs.is65c816 = false;
1452
			is_gen2 = false;
1453
		} else if (!strcmp(argv[0], "-gs")) {
1454
			argc--;
1455
			argv++;
1456
			regs.is65c816 = true;
1457
			is_gen2 = true;
1458
		}
1459
		else if (!strcmp(argv[0], "-rockwell")){
1460
			argc--;
1461
			argv++;
1462
			warn_rockwell = false;
1463
		} else {
1464
			usage();
1465
		}
1466
	}
1467
1468
	// A breakpoint needs a front end that can resume the machine. Without one
1469
	// the first stop is the last: the emulator halts and nothing is listening
1470
	// for a key or a client to start it again.
1471
	//
1472
	// So -bp and -wp *require* a debugger rather than choosing one. They used
1473
	// to switch the text debugger on directly, which was right when it was the
1474
	// only one -- but it meant `-imgui -bp` opened the text overlay over the
1475
	// graphical debugger the user actually asked for, and `-debugport -bp`
1476
	// opened a window on a run that was meant to be headless. Deciding here
1477
	// rather than in the flag handler also makes it independent of the order
1478
	// the flags were written in.
1479
	if (cli_stops_requested && !debug_window_enabled && !imgui_debugger_enabled
1480
	    && !debug_server_is_enabled()) {
1481
		debug_window_enabled = true;
1482
	}
1483
1484
	// Follows the debugger unless asked otherwise, which is where it sat before
1485
	// it became a flag: someone debugging wants symbols for what they load, and
1486
	// someone just running a game gains nothing from reading .dbg files. A
1487
	// policy rather than a snapshot, because the running machine can switch the
1488
	// debugger on itself (emu_write register 0).
1489
	//
1490
	// Either debugger counts. The graphical one's Source and Symbols panels are
1491
	// exactly the things that need this, and gating on -debug alone left them
1492
	// permanently empty under -imgui.
1493
	dbg_load_note_debugger(debugger_enabled || imgui_debugger_enabled);
1494
	dbg_load_set_policy(dbg_auto_opt);
1495
1496
	if (is_gen2) {
1497
		num_banks = NUM_MAX_BANKS;
1498
		num_ram_banks = NUM_MAX_RAM_BANKS;
1499
	}
1500
1501
	SDL_RWops *f = SDL_RWFromFile(rom_path, "rb");
1502
	if (!f) {
1503
		printf("Cannot open %s!\n", rom_path);
1504
		exit(1);
1505
	}
1506
	size_t rom_size = SDL_RWread(f, ROM, ROM_SIZE, 1);
1507
	(void)rom_size;
1508
	SDL_RWclose(f);
1509
1510
	if (nvram_path) {
1511
		SDL_RWops *f = SDL_RWFromFile(nvram_path, "rb");
1512
		if (f) {
1513
			SDL_RWread(f, nvram, 1, sizeof(nvram));
1514
			SDL_RWclose(f);
1515
		}
1516
	}
1517
1518
	if (sdcard_path) {
1519
		sdcard_set_path(sdcard_path);
1520
		if (!hostfs_set) {
1521
			using_hostfs = false;
1522
		}
1523
	}
1524
1525
	if (using_hostfs && !audio_buffers_set) {
1526
#ifdef __EMSCRIPTEN__
1527
		audio_buffers = 8; // wasm has larger buffers in audio.c, so we keep it 8 even w/ HostFS
1528
#else
1529
		audio_buffers = 32;
1530
#endif
1531
	}
1532
1533
	if (sf2_path && has_midi_card) {
1534
		if (midi_card_addr < 0x9f60) {
1535
			fprintf(stderr, "Warning: Serial MIDI card address must be in the range of 9F60-9FF0\n");
1536
		} else {
1537
			midi_init();
1538
			midi_load_sf2((uint8_t *)sf2_path);
1539
		}
1540
	} else if (sf2_path || has_midi_card) {
1541
		fprintf(stderr, "Warning: -sf2 and -midicard must be specified together in order to enable the MIDI synth.\n");
1542
		has_midi_card = false;
1543
	}
1544
1545
	if (cartridge_path) {
1546
		if (!cartridge_load(cartridge_path, !zeroram)) {
1547
			printf("Cannot open %s!\n", cartridge_path);
1548
			exit(1);
1549
		}
1550
	}
1551
1552
	prg_override_start = -1;
1553
	if (prg_path) {
1554
		char *comma = strchr(prg_path, ',');
1555
		if (comma) {
1556
			prg_override_start = (uint16_t)strtol(comma + 1, NULL, 16);
1557
			*comma = 0;
1558
		}
1559
1560
		prg_file = SDL_RWFromFile(prg_path, "rb");
1561
		if (!prg_file) {
1562
			printf("Cannot open %s!\n", prg_path);
1563
			exit(1);
1564
		}
1565
		// Recorded here because the file layer never resolves a name for this
1566
		// one: it is handed the open handle instead. The comma-separated load
1567
		// address, if there was one, has already been stripped above.
1568
		dbg_load_set_prg_path(prg_path);
1569
	}
1570
1571
	if (bas_path) {
1572
		SDL_RWops *bas_file = SDL_RWFromFile(bas_path, "r");
1573
		if (!bas_file) {
1574
			printf("Cannot open %s!\n", bas_path);
1575
			exit(1);
1576
		}
1577
		paste_text = paste_text_data;
1578
		size_t paste_size = SDL_RWread(bas_file, paste_text, 1, sizeof(paste_text_data) - 1);
1579
		if (run_after_load) {
1580
			strncpy(paste_text + paste_size, "\rRUN\r", sizeof(paste_text_data) - paste_size);
1581
		} else {
1582
			paste_text[paste_size] = 0;
1583
		}
1584
		SDL_RWclose(bas_file);
1585
	}
1586
1587
	if (run_test) {
1588
		paste_text = paste_text_data;
1589
		snprintf(paste_text, sizeof(paste_text_data), "TEST %d\r", test_number);
1590
	}
1591
1592
#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
1593
	// Don't disable compositing (on KDE for example)
1594
	// Available since SDL 2.0.8
1595
	SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
1596
#endif
1597
#ifdef __EMSCRIPTEN__
1598
	emscripten_set_main_loop(emscripten_main_loop, 0, 0);
1599
#endif
1600
	// The graphical debugger needs a window, and headless mode never opens one:
1601
	// video_init() below is skipped entirely, so the self-disable inside it
1602
	// never runs. Clearing the flag here keeps the invariant the rest of the
1603
	// code relies on -- the flag is set only if the window exists -- true on
1604
	// this path as well as on the creation-failure path. Left set, the emulator
1605
	// loop routes through DEBUGGetCurrentStatus(), and a breakpoint parks the
1606
	// machine in DMODE_STOP with no window, no events and nothing able to
1607
	// resume it: a silent spin at 100% of a core. (STP is handled separately --
1608
	// stop6502() reports it to the testbench harness instead of stopping.)
1609
	if (headless && imgui_debugger_enabled) {
1610
		imgui_debugger_enabled = false;
1611
		printf("The graphical debugger needs a window; -imgui is ignored in headless mode.\n");
1612
		// Re-state the .dbg policy: it was noted above from a flag that has
1613
		// just been cleared, and without this a headless run would keep loading
1614
		// debug info for a debugger that is not there.
1615
		dbg_load_note_debugger(debugger_enabled || imgui_debugger_enabled);
1616
	}
1617
1618
	if (!headless) {
1619
		// Shows up in the power management area of Linux desktops of applications inhibiting the screensaver
1620
		// As well as the audio mixer
1621
		// Unless hinted, defaults are "My SDL application" and "Playing a game"
1622
#ifdef SDL_HINT_AUDIO_DEVICE_APP_NAME
1623
		SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "Commander X16 Emulator");
1624
#endif
1625
#ifdef SDL_HINT_APP_NAME
1626
		SDL_SetHint(SDL_HINT_APP_NAME, "Commander X16 Emulator");
1627
#endif
1628
#ifdef SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME
1629
		SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, "Emulating modern retro awesomeness");
1630
#endif
1631
		int e = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO | SDL_INIT_TIMER);
1632
		if (e < 0) {
1633
			fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
1634
			exit(-1);
1635
		}
1636
		audio_init(audio_dev_name, audio_buffers);
1637
		video_init(window_scale, screen_x_scale, scale_quality, fullscreen, window_opacity);
1638
	}
1639
1640
	wav_recorder_set_path(wav_path);
1641
1642
	memory_init();
1643
1644
	joystick_init();
1645
1646
	rtc_init(set_system_time);
1647
1648
	machine_reset();
1649
1650
	timing_init();
1651
1652
	instruction_counter = 0;
1653
2980b7b6 Andy McCall ↶ 1654
	if (control_port > 0 && x16_control_start(control_port) != 0) {
1655
		fprintf(stderr, "Failed to start the control port on %d\n", control_port);
1656
		return 1;
1657
	}
1658
9120e874 Andy McCall 1659
#ifdef __EMSCRIPTEN__
1660
	emscripten_cancel_main_loop();
1661
	emscripten_set_main_loop(emscripten_main_loop, 0, 1);
1662
#else
1663
	emulator_loop(NULL);
1664
#endif
1665
1666
	debug_server_shutdown();
1667
	source_view_free();
1668
	dbg_info_free();
1669
	main_shutdown();
1670
	memory_dump_usage_counts();
1671
	return 0;
1672
}
1673
1674
void main_shutdown() {
1675
	if (!headless){
1676
		wav_recorder_shutdown();
1677
		audio_close();
1678
		video_end();
1679
		SDL_Quit();
1680
	}
1681
	if(cartridge_path) {
1682
		cartridge_save_nvram();
1683
		cartridge_unload();
1684
	}
1685
	files_shutdown();
1686
1687
#ifdef PERFSTAT
1688
	for (int pc = 0xc000; pc < sizeof(stat)/sizeof(*stat); pc++) {
1689
		if (stat[pc] == 0) {
1690
			continue;
1691
		}
1692
		char *label = label_for_address(pc);
1693
		if (!label) {
1694
			continue;
1695
		}
1696
		char *original_label = label;
1697
		uint16_t pc2 = pc;
1698
		if (label[0] == '@') {
1699
			label = NULL;
1700
			while (!label || label[0] == '@') {
1701
				pc2--;
1702
				label = label_for_address(pc2);
1703
			}
1704
		}
1705
		printf("%d\t $%04X %s+%d", stat[pc], pc, label, pc-pc2);
1706
		if (pc-pc2 != 0) {
1707
			printf(" (%s)", original_label);
1708
		}
1709
		printf("\n");
1710
	}
1711
#endif
1712
1713
}
1714
1715
bool
1716
set_kernal_status(uint8_t s)
1717
{
1718
	// There is no KERNAL API to write the STATUS variable.
1719
	// But there is code to read it, READST, which should
1720
	// always look like this:
1721
	// 00:.,d6a0 ad 89 02 lda $0289
1722
	// 00:.,d6a3 0d 89 02 ora $0289
1723
	// 00:.,d6a6 8d 89 02 sta $0289
1724
	// We can extract the location of the STATUS variable
1725
	// from it.
1726
1727
	// JMP in the KERNAL API vectors
1728
	if (debug_read6502(0xffb7, 0, 0) != 0x4c) {
1729
		return false;
1730
	}
1731
	// target of KERNAL API vector JMP
1732
	uint16_t readst = debug_read6502(0xffb8, 0, 0) | debug_read6502(0xffb9, 0, 0) << 8;
1733
	if (readst < 0xc000) {
1734
		return false;
1735
	}
1736
	// ad 89 02 lda $0289
1737
	if (debug_read6502(readst, 0, 0) != 0xad) {
1738
		return false;
1739
	}
1740
	// ad 89 02 lda $0289
1741
	if (debug_read6502(readst + 3, 0, 0) != 0x0d) {
1742
		return false;
1743
	}
1744
	// ad 89 02 lda $0289
1745
	if (debug_read6502(readst + 6, 0, 0) != 0x8d) {
1746
		return false;
1747
	}
1748
	uint16_t status0 = debug_read6502(readst+1, 0, 0) | debug_read6502(readst+2, 0, 0) << 8;
1749
	uint16_t status1 = debug_read6502(readst+4, 0, 0) | debug_read6502(readst+5, 0, 0) << 8;
1750
	uint16_t status2 = debug_read6502(readst+7, 0, 0) | debug_read6502(readst+8, 0, 0) << 8;
1751
	// all three addresses must be the same
1752
	if (status0 != status1 || status0 != status2) {
1753
		return false;
1754
	}
1755
1756
	// everything okay, write the status!
1757
	RAM[status0] = s;
1758
	return true;
1759
}
1760
1761
bool
1762
handle_ieee_intercept()
1763
{
1764
	if (no_ieee_intercept) {
1765
		return false;
1766
	}
1767
1768
	if (regs.pc < 0xFEA8 || (is_gen2 && regs.k != 0) || !is_kernal()) {
1769
		return false;
1770
	}
1771
1772
	if (has_serial) {
1773
		// if we do bit-level serial bus emulation, we don't
1774
		// do high-level KERNAL IEEE API interception
1775
		return false;
1776
	}
1777
1778
	if (sdcard_attached && !prg_file && !using_hostfs) {
1779
		// if should emulate an SD card (and don't need to
1780
		// hack a PRG into RAM), we skip HostFS if it uses unit 8
1781
		return false;
1782
	}
1783
1784
	if (sdcard_attached && prg_file && prg_finished_loading && !using_hostfs) {
1785
		// also skip if we should do SD card and we're done
1786
		// with the PRG hack if HostFS uses unit 8
1787
		return false;
1788
	}
1789
1790
	uint64_t base_ticks = SDL_GetPerformanceCounter();
1791
1792
	static int count_unlistn = 0;
1793
	bool handled = true;
1794
	int s = -1;
1795
	switch(regs.pc) {
1796
		case 0xFEA8: { // EXTAPI16
1797
			if (!regs.is65c816 || (regs.status & 0x20)) { // don't handle if not '816 or if m=1
1798
				handled = false; // punt to kernal's 65C816 check and will break if m=1
1799
				break;
1800
			}
1801
			switch(regs.c) {
1802
				case 5: { // XMACPTR
1803
					s=XMACPTR(regs.status & 0x01);
1804
					if (s == -2) {
1805
						handled = false;
1806
					} else if (s == -3) {
1807
						regs.status |= 1; // SEC (unsupported, or in this case, no open context)
1808
						regs.status &= ~0x30; // REP #$30
1809
					} else {
1810
						regs.status &= ~0x31; // REP #$30 and CLC -> supported
1811
					}
1812
					break;
1813
				}
1814
				case 6: { // XMCIOUT
1815
					s=XMCIOUT(regs.status & 0x01);
1816
					if (s == -2) {
1817
						handled = false;
1818
					} else if (s == -3) {
1819
						regs.status |= 1; // SEC (unsupported, or in this case, no open context)
1820
						regs.status &= ~0x30; // REP #$30
1821
					} else {
1822
						regs.status &= ~0x31; // REP #$30 and CLC -> supported
1823
					}
1824
					break;
1825
				}
1826
				default:
1827
					handled = false;
1828
					break;
1829
			}
1830
			break;
1831
		}
1832
		case 0xFEB1: {
1833
			uint16_t count = regs.a;
1834
			s=MCIOUT(regs.yl << 8 | regs.xl, &count, regs.status & 0x01);
1835
			if (s == -2) {
1836
				handled = false;
1837
			} else if (s == -3) {
1838
				regs.status = (regs.status | 1); // SEC (unsupported, or in this case, no open context)
1839
			} else {
1840
				regs.x = count & 0xff;
1841
				regs.y = count >> 8;
1842
				regs.status &= 0xfe; // clear C -> supported
1843
			}
1844
			break;
1845
		}
1846
		case 0xFF44: {
1847
			uint16_t count = regs.a;
1848
			s=MACPTR(regs.yl << 8 | regs.xl, &count, regs.status & 0x01);
1849
			if (s == -2) {
1850
				handled = false;
1851
			} else if (s == -3) {
1852
				regs.status = (regs.status | 1); // SEC (unsupported, or in this case, no open context)
1853
			} else {
1854
				regs.x = count & 0xff;
1855
				regs.y = count >> 8;
1856
				regs.status &= 0xfe; // clear C -> supported
1857
			}
1858
			break;
1859
		}
1860
		case 0xFF93:
1861
			s=SECOND(regs.a);
1862
			if (s == -2) {
1863
				handled = false;
1864
			}
1865
			break;
1866
		case 0xFF96:
1867
			s=TKSA(regs.a);
1868
			if (s == -2) {
1869
				handled = false;
1870
			}
1871
			break;
1872
		case 0xFFA5:
1873
			s=ACPTR(&regs.a);
1874
			if (s == -2) {
1875
				handled = false;
1876
			} else {
1877
				regs.status = (regs.status & ~3) | (!regs.a << 1); // unconditional CLC, and set zero flag based on byte read
1878
			}
1879
			break;
1880
		case 0xFFA8:
1881
			s=CIOUT(regs.a);
1882
			if (s == -2) {
1883
				handled = false;
1884
			} else {
1885
				regs.status = (regs.status & ~1); // unconditonal CLC
1886
			}
1887
			break;
1888
		case 0xFFAB:
1889
			s=UNTLK();
1890
			if (s == -2) {
1891
				handled = false;
1892
			}
1893
			break;
1894
		case 0xFFAE:
1895
			s=UNLSN();
1896
			if (s == -2) {
1897
				handled = false;
1898
			}
1899
			if (prg_file && sdcard_path_is_set() && ++count_unlistn == 4) {
1900
				// after auto-loading a PRG from the host fs,
1901
				// switch to the SD card if requested
1902
				// 4x UNLISTEN:
1903
				//    2x for LOAD"AUTOBOOT.X16*"
1904
				//    2x for LOAD":*"
1905
				prg_finished_loading = true;
1906
				sdcard_attach();
1907
			}
1908
			break;
1909
		case 0xFFB1:
1910
			s=LISTEN(regs.a);
1911
			if (s == -2) {
1912
				handled = false;
1913
			} else {
1914
				regs.status = (regs.status & ~1); // unconditonal CLC
1915
			}
1916
			break;
1917
		case 0xFFB4:
1918
			s=TALK(regs.a);
1919
			if (s == -2) {
1920
				handled = false;
1921
			} else {
1922
				regs.status = (regs.status & ~1); // unconditonal CLC
1923
			}
1924
			break;
1925
		default:
1926
			handled = false;
1927
			break;
1928
	}
1929
1930
	if (handled) {
1931
		// Add the number CPU cycles equivalent to the amount of time that the operation actually took
1932
		// to prevent the emu from warping after a hostfs load
1933
		uint64_t perf_diff = SDL_GetPerformanceCounter() - base_ticks;
1934
		uint32_t missed_ticks = (uint64_t)(perf_diff * 1000000ULL * MHZ) / SDL_GetPerformanceFrequency();
1935
		clockticks6502 += missed_ticks;
1936
		if (s >= 0) {
1937
			if (!set_kernal_status(s)) {
1938
				printf("Warning: Could not set STATUS!\n");
1939
			}
1940
		}
1941
1942
		increment_wrap_at_page_boundary(&regs.sp);
1943
		uint8_t low = debug_read6502(regs.sp, 0, USE_CURRENT_X16_BANK);
1944
		increment_wrap_at_page_boundary(&regs.sp);
1945
		regs.pc = ((debug_read6502(regs.sp, 0, USE_CURRENT_X16_BANK) << 8) | low) + 1;
1946
	}
1947
	return handled;
1948
}
1949
1950
void
1951
emscripten_main_loop(void) {
1952
	emulator_loop(NULL);
1953
}
1954
1955
1956
// Per-instruction KERNAL snooping and host-side plumbing: the $ffff
1957
// termination check, the -echo hook, mouse-cursor tracking, PRG auto-load and
1958
// feeding pasted text into the keyboard buffer. Shared by emulator_loop() and
1959
// emulator_step_during_move() so a window drag does not silently skip any of
1960
// it -- notably -echo output, which would otherwise be lost for good.
1961
// Returns false when the caller should stop emulating.
1962
static bool
1963
emulator_post_step(void)
1964
{
1965
	if (regs.pc == 0xffff) {
1966
		if (save_on_exit) {
1967
			machine_dump("CPU program counter reached $ffff");
1968
		}
1969
		return false;
1970
	}
1971
1972
	// Change this comparison value if ever additional KERNAL
1973
	// API calls are snooped in this routine.
1974
1975
	if (regs.pc >= 0xff68 && (!is_gen2 || regs.k == 0) && is_kernal()) {
1976
		if (regs.pc == 0xff68) {
1977
			kernal_mouse_enabled = !!regs.a;
1978
			SDL_ShowCursor((mouse_grabbed || kernal_mouse_enabled) ? SDL_DISABLE : SDL_ENABLE);
1979
		}
1980
1981
		if (echo_mode != ECHO_MODE_NONE && regs.pc == 0xffd2) {
1982
			uint8_t c = regs.a;
1983
			if (echo_mode == ECHO_MODE_COOKED) {
1984
				if (c == 0x0d) {
1985
					printf("\n");
1986
				} else if (c == 0x0a) {
1987
					// skip
1988
				} else if (c < 0x20 || c >= 0x80) {
1989
					printf("\\X%02X", c);
1990
				} else {
1991
					printf("%c", c);
1992
				}
1993
			} else if (echo_mode == ECHO_MODE_ISO) {
1994
				if (c == 0x0d) {
1995
					printf("\n");
1996
				} else if (c == 0x0a) {
1997
					// skip
1998
				} else if (c < 0x20 || (c >= 0x80 && c < 0xa0)) {
1999
					printf("\\X%02X", c);
2000
				} else {
2001
					print_iso8859_15_char(c);
2002
				}
2003
			} else {
2004
				printf("%c", c);
2005
			}
2006
			fflush(stdout);
2007
		}
2008
2009
		if (regs.pc == 0xffcf) {
2010
			// as soon as BASIC starts reading a line...
2011
			static bool prg_done = false;
2012
2013
			if (prg_file && !prg_done) {
2014
				int loadlen = 0;
2015
				// LOAD":*" will cause the IEEE library
2016
				// to load from "prg_file"
2017
				if (prg_override_start >= 0) {
2018
					loadlen = snprintf(paste_text_data, sizeof(paste_text_data), "LOAD\":*\",%d,1,$%04X\r", ieee_unit, prg_override_start);
2019
				} else {
2020
					loadlen = snprintf(paste_text_data, sizeof(paste_text_data), "LOAD\":*\",%d,1\r", ieee_unit);
2021
				}
2022
				paste_text = paste_text_data;
2023
				prg_done = true;
2024
2025
				if (run_after_load) {
2026
					if (prg_override_start >= 0) {
2027
						snprintf(paste_text_data + loadlen, sizeof(paste_text_data) - loadlen, "SYS$%04X\r", prg_override_start);
2028
					} else {
2029
						snprintf(paste_text_data + loadlen, sizeof(paste_text_data) - loadlen, "RUN\r");
2030
					}
2031
				}
2032
			}
2033
			else if (testbench && !test_init_complete){
2034
				snprintf(paste_text_data, sizeof(paste_text_data), "SYS65533\r");
2035
				paste_text = paste_text_data;
2036
				test_init_complete=true;
2037
			}
2038
2039
			if (paste_text) {
2040
				// ...paste BASIC code into the keyboard buffer
2041
				pasting_bas = true;
2042
				if (warp_pastes) machine_set_warp(true);
2043
			}
2044
		}
2045
2046
	}
2047
#if 0 // enable this for slow pasting
2048
	if (!(instruction_counter % 100000))
2049
#endif
2050
	while (pasting_bas && BRAM[NDX - 0xa000] < 10 && !(regs.status & 0x04)) {
2051
		uint32_t c;
2052
		int e = 0;
2053
2054
		if (paste_text[0] == '\\' && paste_text[1] == 'X' && paste_text[2] && paste_text[3]) {
2055
			uint8_t hi = strtol((char[]){paste_text[2], 0}, NULL, 16);
2056
			uint8_t lo = strtol((char[]){paste_text[3], 0}, NULL, 16);
2057
			c = hi << 4 | lo;
2058
			paste_text += 4;
2059
		} else {
2060
			paste_text = utf8_decode(paste_text, &c, &e);
2061
			c = iso8859_15_from_unicode(c);
2062
		}
2063
		if (c && !e) {
2064
			BRAM[KEYD - 0xa000 + BRAM[NDX - 0xa000]] = c;
2065
			BRAM[NDX - 0xa000]++;
2066
		} else {
2067
			pasting_bas = false;
2068
			if (warp_pastes) machine_set_warp(false);
2069
			paste_text = NULL;
2070
			if (clipboard_buffer) {
2071
				SDL_free(clipboard_buffer);
2072
				clipboard_buffer = NULL;
2073
			}
2074
		}
2075
	}
2076
2077
	return true;
2078
}
2079
2080
// Set when emulator_post_step() reports that emulation should stop (the guest
2081
// reached $ffff). The drag loop cannot exit the program from inside the OS
2082
// modal loop, so it latches this and stops stepping; emulator_loop() unwinds
2083
// once the drag is over.
2084
static bool emulator_stop_requested = false;
2085
2086
// Longest we will spend emulating inside a single timer tick. The OS cannot
2087
// deliver mouse input to the drag while we are in the window procedure, so a
2088
// long slice is felt directly as a jerky window. A frame that does not finish
2089
// in time simply continues on the next tick.
2090
#define MOVE_SLICE_BUDGET_MS 6
2091
2092
// Record the instruction about to execute into the disassembly code map. Call
2093
// immediately before step6502() at every execution site so coverage of real
2094
// instruction starts (and the M/X/E flag state that fixes 65C816 operand
2095
// widths) is complete. The emulation-mode width bits are folded into the stored
2096
// status so it can be fed straight into disasm()'s implied_status.
2097
static inline void
2098
code_map_record_current(void)
2099
{
2100
	uint8_t status = regs.status;
2101
	// step6502() forces 8-bit memory/index widths whenever the emulation flag
2102
	// is set (true for the 65C02 always, and the 65C816 in emulation mode), so
2103
	// mirror that here — otherwise a record taken just after a PLP/RTI that
2104
	// pulled the width bits clear would disassemble past addresses too wide.
2105
	if (regs.e)
2106
		status |= (uint8_t)(FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH);
2107
	code_map_record(regs.pc, regs.k, memory_get_ram_bank(), memory_get_rom_bank(), status);
2108
}
2109
2110
// Keep the budget module in step with the machine. The video mode can change
2111
// under the guest (VGA and NTSC have different frame periods) and -mhz sets the
2112
// clock, so the frame period is recomputed rather than latched once at startup.
2113
//
2114
// The budget deliberately follows the machine's NATIVE clock, not
2115
// timing_get_speed_khz(): the speed control exists so a routine can be watched
2116
// at 25 kHz, and a budget that shrank to match would report every frame as a
2117
// catastrophic overrun for a machine that is running perfectly well.
2118
static inline void
2119
perf_budget_sync_machine(void)
2120
{
2121
	// PIXEL_FREQ is 25 MHz, so cycles = mhz * dot_clocks / 25.
2122
	const uint32_t dots = video_frame_dot_clocks();
2123
	perf_budget_configure((uint32_t)MHZ * 1000u,
2124
	                      (uint32_t)(((uint64_t)MHZ * dots) / 25u),
2125
	                      video_frame_scanlines());
2126
2127
	// The bandwidth model needs the same scanline count, and nothing else: its
2128
	// per-line window is VERA's own 800 clocks, which no emulator setting can
2129
	// move.
2130
	vera_bandwidth_configure(video_frame_scanlines());
2131
}
2132
2133
// Record one executed instruction against the performance budget. `pc`/`pbank`
2134
// are the instruction that just RAN -- regs.pc has already moved on to the next
2135
// one by the time this is called.
2136
//
2137
// The enabled test is here rather than only inside perf_budget_step() because
2138
// arguments are evaluated before the callee can decline them: cpu_irq_depth()
2139
// is a real call, and leaving it to be made on every instruction of every run
2140
// would be the whole cost of profiling paid by machines that are not profiling.
2141
static inline void
2142
perf_budget_record(uint32_t clocks, uint16_t pc, uint8_t pbank)
2143
{
2144
	if (!perf_budget_enabled)
2145
		return;
2146
	perf_budget_step(clocks, pc, pbank, waiting != 0, cpu_irq_depth());
2147
}
2148
2149
// Close a frame, charging it the host time that emulating and drawing it took.
2150
// Called before timing_update(), so the throttle's sleep is excluded: what a
2151
// perf panel wants to know is how long the work took, not how long we then
2152
// waited around to stay in sync.
2153
static inline void
2154
perf_budget_close_frame(void)
2155
{
2156
	static uint64_t mark = 0;
2157
	uint32_t        host_us = 0;
2158
	const uint64_t  now  = SDL_GetPerformanceCounter();
2159
	const uint64_t  freq = SDL_GetPerformanceFrequency();
2160
	if (mark && freq)
2161
		host_us = (uint32_t)(((now - mark) * 1000000ULL) / freq);
2162
	mark = now;
2163
2164
	perf_budget_sync_machine();
2165
	perf_budget_frame_end(host_us);
2166
}
2167
2168
// Advance the emulator from inside the OS modal window move/resize loop (see
2169
// video_win32.c). That loop blocks emulator_loop() entirely, so without this
2170
// the machine would freeze while the window is dragged.
2171
//
2172
// Two things matter for the drag to stay smooth: never sleep here, and never
2173
// run for long. We pace by checking how far ahead of the wall clock we are and
2174
// skipping the tick if we are ahead, rather than sleeping the deficit away, and
2175
// we cap how much emulation one tick may do. Shares old_clockticks6502 and the
2176
// timing accumulators with emulator_loop(), so emulation resumes seamlessly
2177
// when the drag ends.
2178
void
2179
emulator_step_during_move(void)
2180
{
2181
	if (headless || emulator_stop_requested) {
2182
		return;
2183
	}
2184
	// Presenting can dispatch messages, which can land back here. Emulating
2185
	// re-entrantly would corrupt the clock accounting.
2186
	static bool in_step = false;
2187
	if (in_step) {
2188
		return;
2189
	}
2190
	// Leave the machine alone whenever any debugger is in play. Breakpoints are
2191
	// honoured by DEBUGGetCurrentStatus(), which also drives the debugger's own
2192
	// window and event pump and so cannot be called from inside the OS modal
2193
	// loop; stepping here without it would run straight through breakpoints.
2194
	// Dragging with -debug therefore behaves as it always has -- and the same
2195
	// has to hold for -imgui, whose window is move-hooked too, so a drag of
2196
	// either window cannot run the CPU behind the UI's back.
2197
	//
2198
	// The DAP server counts too, and for the same reason -- a client's
2199
	// breakpoints are the same breakpoints. The guest can clear
2200
	// debugger_enabled by writing $9FB0, so testing that alone would let a drag
2201
	// step past a breakpoint a client is waiting on.
2980b7b6 Andy McCall ↶ 2202
	// And the control port: a harness stepping frames owns the machine's time.
2203
	if (debugger_enabled || imgui_debugger_enabled || debug_server_is_enabled() || control_port_active) {
9120e874 Andy McCall 2204
		return;
2205
	}
2206
2207
	// Already ahead of real time: do nothing at all. This is the common case
2208
	// now that a fast drag calls us on every mouse message, and it has to stay
2209
	// cheap -- no emulation, and no present either.
2210
	if (!warp_mode && timing_lead_us() > 0) {
2211
		return;
2212
	}
2213
2214
	in_step = true;
2215
2216
	bool new_frame = false;
2217
	uint32_t steps = 0;
2218
	const uint32_t slice_start = SDL_GetTicks();
2219
	do {
2220
		if (handle_ieee_intercept()) {
2221
			// Polled here too: this loop can run many intercepts without ever
2222
			// returning to the main loop, and only one completed load is held
2223
			// at a time, so a second would overwrite the first. Reachable only
2224
			// via -dbgauto without -debug, since dragging bails out above when
2225
			// the debugger is enabled.
2226
			dbg_load_poll();
2227
			continue;
2228
		}
2229
		instruction_counter += waiting ^ 0x1;
2230
		const uint16_t perf_pc    = regs.pc;
2231
		const uint8_t  perf_pbank = regs.k;
2232
		step6502();
2233
		uint32_t clocks = clockticks6502 - old_clockticks6502;
2234
		old_clockticks6502 = clockticks6502;
2235
		perf_budget_record(clocks, perf_pc, perf_pbank);
2236
		via1_step(clocks);
2237
		vera_spi_step(MHZ, clocks);
2238
		if (has_serial) {
2239
			serial_step(clocks);
2240
		}
2241
		if (has_via2) {
2242
			via2_step(clocks);
2243
		}
2244
		new_frame |= video_step(MHZ, clocks, false);
2245
		for (uint32_t i = 0; i < clocks; i++) {
2246
			i2c_step();
2247
		}
2248
		rtc_step(clocks);
2249
		audio_step(clocks);
2250
		midi_serial_step(clocks);
2251
		if (ym2151_irq_support) {
2252
			audio_render();
2253
		}
2254
		if (video_get_irq_out() || via1_irq() || (has_via2 && via2_irq()) || (ym2151_irq_support && YM_irq()) || (has_midi_card && midi_serial_irq())) {
2255
			irq6502();
2256
		}
2257
		if (!emulator_post_step()) {
2258
			emulator_stop_requested = true;
2259
			break;
2260
		}
2261
		// Reading the clock every instruction would cost more than it saves.
2262
		if ((++steps & 0x3FF) == 0 && SDL_GetTicks() - slice_start >= MOVE_SLICE_BUDGET_MS) {
2263
			break;
2264
		}
2265
	} while (!new_frame && steps < 4000000);
2266
2267
	// Only worth presenting once the frame is actually finished; a partial
2268
	// frame looks no different from the one already on screen.
2269
	if (new_frame) {
2270
		video_present_no_input();
2271
		perf_budget_close_frame();
2272
	}
2273
	timing_update_no_sleep();
2274
2275
	in_step = false;
2276
}
2277
2278
void *
2279
emulator_loop(void *param)
2280
{
2281
	old_clockticks6502 = clockticks6502;
2282
	for (;;) {
2283
		// A drag may have run the guest to $ffff while the OS held us in its
2284
		// modal loop; finish unwinding now that we are back in control.
2285
		if (emulator_stop_requested) break;
2286
2287
		if (smc_requested_reset) machine_reset();
2980b7b6 Andy McCall ↶ 2288
2289
		// The control port's requests are served here, between instructions,
2290
		// so a read sees a whole machine; and its pause holds the CPU here.
2291
		retro_control_service();
2292
		if (!retro_control_running()) {
2293
			SDL_Delay(1);
2294
			continue;
2295
		}
9120e874 Andy McCall 2296
2297
		if (testbench && regs.pc == 0xfffd){
2298
			testbench_init();
2299
		}
2300
2301
		// The graphical debugger needs this too: DEBUGGetCurrentStatus() is what
2302
		// detects breakpoint arrival and completes a step, and it delegates the
2303
		// pause loop to whichever UI is up. Tested against the window rather
2304
		// than the flag alone, so that no path can park the machine in a pause
2305
		// loop that has no UI to resume it.
2306
		//
2307
		// The guest can clear debugger_enabled by writing $9FB0. With a DAP
2308
		// client attached that must not take the debugger down with it: nothing
2309
		// would poll the socket again, breakpoints would stop being tested, and
2310
		// the client would hang with no way back. Running the status hook here
2311
		// rather than polling separately also keeps the socket work behind the
2312
		// 1-in-1000 throttle inside it -- a bare poll in this loop is one
2313
		// syscall per emulated instruction.
2314
		if (debugger_enabled
2315
		    || (imgui_debugger_enabled && video_debug_ui_available())
2316
		    || debug_server_is_enabled()) {
2317
			int dbgCmd = DEBUGGetCurrentStatus();
2318
			if (dbgCmd > 0) continue;
2319
			if (dbgCmd < 0) break;
2320
		}
2321
2322
#ifdef PERFSTAT
2323
2324
//		if (memory_get_rom_bank() == 3) {
2325
//			stat[pc]++;
2326
//		}
2327
		if (memory_get_rom_bank() == 3) {
2328
			static uint8_t old_sp;
2329
			static uint16_t base_pc;
2330
			if (regs.sp < old_sp) {
2331
				base_pc = pc;
2332
			}
2333
			old_sp = regs.sp;
2334
			stat[base_pc]++;
2335
		}
2336
#endif
2337
2338
#ifdef TRACE
2339
		if (regs.pc == trace_address && trace_address != 0) {
2340
			trace_mode = true;
2341
		}
2342
		if (trace_mode && !waiting) {
2343
			char *lst = lst_for_address(regs.pc);
2344
			if (lst) {
2345
				char *lf;
2346
				while ((lf = strchr(lst, '\n'))) {
2347
					for (int i = 0; i < 120; i++) {
2348
						printf(" ");
2349
					}
2350
					if (regs.is65c816) {
2351
						printf("        "); // 8 extra width
2352
					}
2353
					for (char *c = lst; c < lf; c++) {
2354
						printf("%c", *c);
2355
					}
2356
					printf("\n");
2357
					lst = lf + 1;
2358
				}
2359
			}
2360
2361
			printf("[%8d] ", instruction_counter);
2362
2363
			int32_t eff_addr;
2364
2365
			char *label = label_for_address(regs.pc);
2366
			int label_len = label ? strlen(label) : 0;
2367
			if (label) {
2368
				printf("%s", label);
2369
			}
2370
			for (int i = 0; i < 20 - label_len; i++) {
2371
				printf(" ");
2372
			}
2373
2374
			if (regs.pc >= 0xc000 && regs.k == 0) {
2375
				printf (" %02x", memory_get_rom_bank());
2376
			} else if (regs.pc >= 0xa000 && regs.k == 0) {
2377
				printf (" %02x", memory_get_ram_bank());
2378
			} else {
2379
				printf (" --");
2380
			}
2381
2382
			printf(":.,%04x ", regs.pc);
2383
2384
			char disasm_line[15];
2385
			int len = disasm(regs.pc, regs.k, RAM, disasm_line, sizeof(disasm_line), -1, regs.status, &eff_addr);
2386
			for (int i = 0; i < len; i++) {
2387
				printf("%02x ", debug_read6502(regs.pc + i, regs.k, USE_CURRENT_X16_BANK));
2388
			}
2389
			for (int i = 0; i < 9 - 3 * len; i++) {
2390
				printf(" ");
2391
			}
2392
			printf("%s", disasm_line);
2393
			for (int i = 0; i < 15 - strlen(disasm_line); i++) {
2394
				printf(" ");
2395
			}
2396
			if (regs.is65c816) {
2397
				printf("C=$%04x X=$%04x Y=$%04x S=$%04x P=", regs.c, regs.x, regs.y, regs.sp);
2398
				for (int i = 7; i >= 0; i--) {
2399
					printf("%c", (regs.status & (1 << i)) ? "czidxmvn"[i] : '-');
2400
				}
2401
2402
				putchar(regs.e ? 'e' : '-');
2403
			} else {
2404
				printf("A=$%02x X=$%02x Y=$%02x S=$%02x P=", regs.a, regs.xl, regs.yl, regs.sp & 0xff);
2405
				for (int i = 7; i >= 0; i--) {
2406
					printf("%c", (regs.status & (1 << i)) ? "czidb-vn"[i] : '-');
2407
				}
2408
			}
2409
2410
			if (eff_addr == 0x9f23 && regs.k == 0) {
2411
				printf(" VRAM=$%05x ", video_get_address(0));
2412
			} else if (eff_addr == 0x9f24 && regs.k == 0) {
2413
				printf(" VRAM=$%05x ", video_get_address(1));
2414
			} else if (eff_addr >= 0xc000 && regs.k == 0) {
2415
				printf(" EA=$%02x:%04x ", memory_get_rom_bank(), eff_addr);
2416
			} else if (eff_addr >= 0xa000 && regs.k == 0) {
2417
				printf(" EA=$%02x:%04x ", memory_get_ram_bank(), eff_addr);
2418
			} else if (eff_addr >= 0) {
2419
				printf(" EA=$--:%04x ", eff_addr);
2420
			} else {
2421
				printf("             ");
2422
			}
2423
2424
			if (lst) {
2425
				printf("%s      %s", regs.is65c816 ? "" : " ", lst);
2426
			}
2427
2428
			printf("\n");
2429
		}
2430
#endif
2431
2432
		if (handle_ieee_intercept()) {
2433
			// The only place a load can complete, so the only place worth
2434
			// looking. Polling here rather than once per instruction also
2435
			// makes the single pending slot self-evidently safe: an intercept
2436
			// finishes at most one channel, and it is drained before the next
2437
			// one can run.
2438
			dbg_load_poll();
2439
			continue;
2440
		}
2441
2442
		instruction_counter += waiting ^ 0x1;
2443
2444
		// The code map is what the disassembly views anchor on: without it every
2445
		// line above the PC is decoded from a mid-instruction byte. The ImGui
2446
		// Disassembly panel is built entirely on it, so it has to be recorded
2447
		// for -imgui too, not just -debug.
2448
		if ((debugger_enabled || imgui_debugger_enabled) && !waiting) {
2449
			code_map_record_current();
2450
		}
2451
		const uint16_t perf_pc    = regs.pc;
2452
		const uint8_t  perf_pbank = regs.k;
2453
		step6502();
2454
		uint32_t clocks = clockticks6502 - old_clockticks6502;
2455
		old_clockticks6502 = clockticks6502;
2456
		perf_budget_record(clocks, perf_pc, perf_pbank);
2457
		bool new_frame = false;
2458
		via1_step(clocks);
2459
		vera_spi_step(MHZ, clocks);
2460
		if (has_serial) {
2461
			serial_step(clocks);
2462
		}
2463
		if (has_via2) {
2464
			via2_step(clocks);
2465
		}
2466
		if (!headless) {
2467
			new_frame |= video_step(MHZ, clocks, false);
2468
		}
2469
2470
		for (uint32_t i = 0; i < clocks; i++) {
2471
			i2c_step();
2472
		}
2473
		rtc_step(clocks);
2474
2475
		if (!headless) {
2476
			audio_step(clocks);
2477
		}
2478
2479
		midi_serial_step(clocks);
2480
2481
		if (!headless && new_frame) {
2482
			if (nvram_dirty && nvram_path) {
2483
				SDL_RWops *f = SDL_RWFromFile(nvram_path, "wb");
2484
				if (f) {
2485
					SDL_RWwrite(f, nvram, 1, sizeof(nvram));
2486
					SDL_RWclose(f);
2487
				}
2488
				nvram_dirty = false;
2489
			}
2490
2491
			if (!video_update()) {
2492
				break;
2493
			}
2494
2495
			// Before timing_update(), which sleeps off whatever surplus the
2496
			// frame left: the budget wants the cost of the work, not the wait.
2497
			perf_budget_close_frame();
2980b7b6 Andy McCall ↶ 2498
2499
			// After the budget closes the frame, so a /step that ends here
2500
			// finds /perf's current frame is this one.
2501
			retro_control_on_frame();
9120e874 Andy McCall 2502
2503
			timing_update();
2504
#ifdef __EMSCRIPTEN__
2505
			// After completing a frame we yield back control to the browser to stay responsive
2506
			return 0;
2507
#endif
2508
		}
2509
2510
		// The optimization from the opportunistic batching of audio rendering
2511
		// is lost if we need to track the YM2151 IRQ, so it has been made a
2512
		// command-line switch that's disabled by default.
2513
		if (ym2151_irq_support) {
2514
			audio_render();
2515
		}
2516
2517
		if (video_get_irq_out() || via1_irq() || (has_via2 && via2_irq()) || (ym2151_irq_support && YM_irq()) || (has_midi_card && midi_serial_irq())) {
2518
//			printf("IRQ!\n");
2519
			irq6502();
2520
		}
2521
2522
		if (!emulator_post_step()) {
2523
			break;
2524
		}
2525
	}
2526
2527
	return 0;
2528
}