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