39 #define ROM_Z80_PC_LOAD_TRIGGER 0x3994
50 static Uint32 primo_palette_white;
51 static Uint32 primo_palette[0x100];
52 static int border_top, border_bottom, border_left, border_right;
53 static int primo_screen = 0;
54 static int nmi_enabled = 0;
55 static int nmi_status = 0;
56 static void (*renderer)(void);
58 static int primo_model_set;
59 static const char *primo_model_set_str;
61 static int cpu_clocks_per_scanline;
62 static int cpu_clocks_per_audio_sample;
64 static int cpu_clock_wanted;
66 static Uint64 all_cycles_spent;
68 static int emu_loop_notification;
70 #define EMU_LOOP_NMI_NOTIFY 1
71 #define EMU_LOOP_LOAD_NOTIFY 2
72 #define EMU_LOOP_UPDATE_NOTIFY 4
73 #define EMU_LOOP_DISASM_NOTIFY 8
77 #define VBLANK_START_SCANLINE 256
78 #define PAL_LINE_FREQ 15625
82 #define JOY_CLOCKING_TIMEOUT_MICROSECS 272
92 #define AUDIO_SAMPLING_FREQ (PAL_LINE_FREQ * 2)
93 #define AUDIO_PULSE_SAMPLES_MAX_PASS (AUDIO_SAMPLING_FREQ / 32)
97 static Uint64 beeper_last_changed;
98 static SDL_AudioDeviceID audio;
99 static int audio_enabled = 1;
113 static int primo_read_joy (
int on,
int off )
147 if ((port16 & 0xFF) < 0x40) {
148 static int random_2 = 0;
152 return (((~
kbd_matrix[(port16 >> 3) & 7]) >> (port16 & 7)) & 1) | vblank;
153 }
else if ((port16 & 0xFF) < 0x80) {
154 DEBUG(
"JOY: read state at step=%d" NL,
joy.step);
155 return primo_read_joy(0, 1) |
serial;
162 static void manage_nmi (
void )
166 int new_status = nmi_enabled && vblank;
167 if (new_status && !nmi_status) {
169 DEBUG(
"NMI: triggering edge" NL);
171 nmi_status = new_status;
179 if ((port16 & 0xFF) < 0x40) {
180 primo_screen = (
value & 8) ? 0x2000 : 0x0000;
181 nmi_enabled =
value & 128;
183 if ((
value & 16) != beeper) {
184 static int rounding_error = 0;
185 Uint64 no_of_samples = (rounding_error + all_cycles_spent - beeper_last_changed) / cpu_clocks_per_audio_sample;
186 rounding_error = (rounding_error + all_cycles_spent - beeper_last_changed) % cpu_clocks_per_audio_sample;
187 if (no_of_samples <= AUDIO_PULSE_SAMPLES_MAX_PASS && no_of_samples > 0 && audio && audio_enabled) {
188 Uint8 samples[no_of_samples];
190 int ret = SDL_QueueAudio(audio, samples, no_of_samples);
192 DEBUGPRINT(
"AUDIO: DATA: ERROR: %s" NL, SDL_GetError());
194 DEBUG(
"AUDIO: DATA: queued" NL);
198 beeper_last_changed = all_cycles_spent;
203 joy.step = (all_cycles_spent -
joy.last_clocked >=
joy.clocking_timeout) ? 0 : (
joy.step + 1) & 7;
204 joy.last_clocked = all_cycles_spent;
209 }
else if ((port16 & 0xFF) < 0x80) {
211 }
else if ((port16 & 0xFF) == 0xFD) {
232 #define VIRTUAL_SHIFT_POS 0x03
248 { SDL_SCANCODE_Y, 0x00 },
249 { SDL_SCANCODE_UP, 0x01 },
250 { SDL_SCANCODE_S, 0x02 },
251 { SDL_SCANCODE_LSHIFT, 0x03 }, { SDL_SCANCODE_RSHIFT, 0x03 },
252 { SDL_SCANCODE_E, 0x04 },
254 { SDL_SCANCODE_W, 0x06 },
255 { SDL_SCANCODE_LCTRL, 0x07 },
256 { SDL_SCANCODE_D, 0x10 },
257 { SDL_SCANCODE_3, 0x11 },
258 { SDL_SCANCODE_X, 0x12 },
259 { SDL_SCANCODE_2, 0x13 },
260 { SDL_SCANCODE_Q, 0x14 },
261 { SDL_SCANCODE_1, 0x15 },
262 { SDL_SCANCODE_A, 0x16 },
263 { SDL_SCANCODE_DOWN, 0x17 },
264 { SDL_SCANCODE_C, 0x20 },
266 { SDL_SCANCODE_F, 0x22 },
268 { SDL_SCANCODE_R, 0x24 },
270 { SDL_SCANCODE_T, 0x26 },
271 { SDL_SCANCODE_7, 0x27 },
272 { SDL_SCANCODE_H, 0x30 },
273 { SDL_SCANCODE_SPACE, 0x31 },
274 { SDL_SCANCODE_B, 0x32 },
275 { SDL_SCANCODE_6, 0x33 },
276 { SDL_SCANCODE_G, 0x34 },
277 { SDL_SCANCODE_5, 0x35 },
278 { SDL_SCANCODE_V, 0x36 },
279 { SDL_SCANCODE_4, 0x37 },
280 { SDL_SCANCODE_N, 0x40 },
281 { SDL_SCANCODE_8, 0x41 },
282 { SDL_SCANCODE_Z, 0x42 },
284 { SDL_SCANCODE_U, 0x44 },
285 { SDL_SCANCODE_0, 0x45 },
286 { SDL_SCANCODE_J, 0x46 },
288 { SDL_SCANCODE_L, 0x50 },
289 { SDL_SCANCODE_MINUS, 0x51 },
290 { SDL_SCANCODE_K, 0x52 },
291 { SDL_SCANCODE_PERIOD, 0x53 },
292 { SDL_SCANCODE_M, 0x54 },
293 { SDL_SCANCODE_9, 0x55 },
294 { SDL_SCANCODE_I, 0x56 },
295 { SDL_SCANCODE_COMMA, 0x57 },
297 { SDL_SCANCODE_APOSTROPHE, 0x61 },
298 { SDL_SCANCODE_P, 0x62 },
300 { SDL_SCANCODE_O, 0x64 },
301 { SDL_SCANCODE_HOME, 0x65 },
303 { SDL_SCANCODE_RETURN, 0x67 },
305 { SDL_SCANCODE_LEFT, 0x71 },
309 { SDL_SCANCODE_RIGHT, 0x75 },
311 { SDL_SCANCODE_ESCAPE, 0x77 },
326 static void set_border_geometry (
int xres,
int yres )
333 FATAL(
"Internal error: too small target texture!");
337 #ifdef XEMU_FILES_SCREENSHOT_SUPPORT
339 static inline void do_pending_screenshot (
void )
344 if (!xemu_screenshot_png(
353 const char *p = strrchr(xemu_screenshot_full_path,
DIRSEP_CHR);
355 OSD(-1, -1,
"%s", p + 1);
364 static void render_primo_bw_screen (
void )
369 for (
int y = 0;
y < border_top;
y++) {
370 for (
int x = 0;
x < 256 + border_left + border_right;
x++)
371 *pix++ = primo_palette[0];
374 for (
int y = 0;
y < 192;
y++) {
375 for (
int x = 0;
x < border_left;
x++)
376 *pix++ = primo_palette[0];
377 for (
int x = 0;
x < 32;
x++)
378 for (
int z = 0, b = *scr++; z < 8; z++, b <<= 1)
379 *pix++ = b & 0x80 ? primo_palette_white : primo_palette[0];
380 for (
int x = 0;
x < border_right;
x++)
381 *pix++ = primo_palette[0];
384 for (
int y = 0;
y < border_bottom;
y++) {
385 for (
int x = 0;
x < 256 + border_left + border_right;
x++)
386 *pix++ = primo_palette[0];
389 #ifdef XEMU_FILES_SCREENSHOT_SUPPORT
390 do_pending_screenshot();
397 static void render_primo_c_screen (
void )
423 int attrstartpos = 1;
424 int attrincelstart = 2;
427 scr =
memory.vid16k + primo_screen + 0x2000 - yres * 32;
428 Uint8 *col = scr - (yres / attrh) * 32;
429 Uint8 *pal_bg_sel =
memory.vid16k + primo_screen + 32;
430 Uint8 *pal_fg_sel =
memory.vid16k + primo_screen + 32 + 16;
431 set_border_geometry(256, yres);
432 for (
int y = 0;
y < border_top;
y++) {
433 for (
int x = 0;
x < 256 + border_left + border_right;
x++)
434 *pix++ = primo_palette[0];
437 Uint32 foregrounds[64], backgrounds[64];
438 for (
int y = 0;
y < yres;
y++) {
440 for (
int x = 0;
x < 64; ) {
442 foregrounds[
x] = primo_palette[pal_fg_sel[b >> 4]];
443 backgrounds[
x] = primo_palette[pal_bg_sel[b >> 4]];
445 foregrounds[
x] = primo_palette[pal_fg_sel[b & 15]];
446 backgrounds[
x] = primo_palette[pal_bg_sel[b & 15]];
449 for (
int x = 0;
x < border_left;
x++)
450 *pix++ = primo_palette[0];
451 int attrpos = attrstartpos;
452 int attrcountincel = attrincelstart;
453 for (
int x = 0;
x < 32;
x++)
454 for (
int z = 0, b = *scr++; z < 8; z++, b <<= 1) {
455 *pix++ = b & 0x80 ? foregrounds[attrpos] : backgrounds[attrpos];
457 if (attrcountincel == attrw) {
462 for (
int x = 0;
x < border_right;
x++)
463 *pix++ = primo_palette[0];
465 if (attrline == attrh)
469 for (
int y = 0;
y < border_bottom;
y++) {
470 for (
int x = 0;
x < 256 + border_left + border_right;
x++)
471 *pix++ = primo_palette[0];
474 #ifdef XEMU_FILES_SCREENSHOT_SUPPORT
475 do_pending_screenshot();
500 int baddr =
data[i + 1] | (
data[i + 2] << 8);
505 int bsize =
data[i + 3] | (
data[i + 4] << 8);
507 baddr +=
memory.main[0x40A4] | (
memory.main[0x40A5] << 8);
509 memory.main[0x40F9] = (baddr + bsize + 1) & 0xFF;
510 memory.main[0x40FA] = (baddr + bsize + 1) >> 8;
512 }
else if (btype != 0xD5 && btype != 0xD9) {
513 ERROR_WINDOW(
"Invalid PRI file, unknown block type $%02X @ %d", btype, i);
516 if (i >=
size - bsize)
518 DEBUGPRINT(
" ... ADDR=%04X SIZE=%04X" NL, baddr, bsize);
527 memcpy(
memory.main + baddr,
data + i + 5, bsize);
537 static int pri_load (
const char *file_name,
int wet_run )
539 if (!file_name || !*file_name)
541 int file_size =
xemu_load_file(file_name, NULL, 10, 0xC000,
"Cannot open/use PRI file");
550 static void set_title_model_details (
void )
552 static char title_str_id[64];
553 sprintf(title_str_id,
"(model %s, %.2fMHz)", primo_model_set_str, (
float)cpu_clock / 1000000.0);
558 static int set_cpu_hz (
int hz )
560 cpu_clock_wanted = hz;
563 DEBUGPRINT(
"CLOCK: CPU: clock speed set to %.2f MHz (%d CPU cycles per scanline)" NL, cpu_clock / 1000000.0, cpu_clocks_per_scanline);
568 set_title_model_details();
573 static void emulation_loop (
void )
575 static int cycles = 0;
577 Uint64 all_cycles_old = all_cycles_spent;
582 all_cycles_spent += op_cycles;
607 all_cycles_spent += op_cycles;
616 int ret = pri_load(
configdb.pri_name, 1);
621 DEBUGPRINT(
"PRI: loaded, no Z80 PC change requested" NL);
643 if (scanline == 311) {
644 cycles += cpu_clocks_per_scanline / 2;
646 }
else if (scanline == 312) {
647 cycles += cpu_clocks_per_scanline;
656 cycles += cpu_clocks_per_scanline;
664 FATAL(
"Emulation problem, underflow of cpu cycles / scanline tracking counter!");
677 static int choice = 0;
679 choice =
QUESTION_WINDOW(
"Load as PRI now|Load as PRI always|Cancel for now",
"What should I do with the dropped file?");
691 OSD(-1, -1,
"File has been dropped");
696 static const char primo_model_name_0[] =
"A32";
697 static const char primo_model_name_1[] =
"A48";
698 static const char primo_model_name_2[] =
"A64";
699 static const char primo_model_name_3[] =
"B32";
700 static const char primo_model_name_4[] =
"B48";
701 static const char primo_model_name_5[] =
"B64";
702 static const char primo_model_name_6[] =
"C";
703 static const char *primo_model_names[] = { primo_model_name_0, primo_model_name_1, primo_model_name_2, primo_model_name_3, primo_model_name_4, primo_model_name_5, primo_model_name_6, NULL };
706 static int set_model (
const char *model_id,
int do_load_rom )
709 while (primo_model_names[
id] && strcasecmp(model_id, primo_model_names[
id]))
711 if (!primo_model_names[
id]) {
712 ERROR_WINDOW(
"Unknown Primo model requested: %s", model_id);
716 sprintf(model_desc,
"Primo-%c%s", toupper(primo_model_names[
id][0]), primo_model_names[
id] + 1);
717 DEBUGPRINT(
"PRIMO: trying to initialize to model: %s" NL, model_desc);
720 DEBUGPRINT(
"ROM: trying to load forced (by config) ROM, regardless of the selected model" NL);
721 if (
xemu_load_file(
configdb.rom_fn,
memory.main, 0x4000, 0x4000,
"This is the selected primo ROM. Without it, Xemu won't work.\nInstall it, or use -rom CLI switch to specify another path.") < 0)
726 sprintf(rom_file,
"#primo-%c%s.rom", tolower(primo_model_names[
id][0]), primo_model_names[
id] + 1);
727 sprintf(rom_err,
"Cannot load default %s ROM file.\nYou can try to force one with the -rom CLI option.", model_desc);
728 DEBUGPRINT(
"ROM: trying to load model dependent default ROM file" NL);
738 renderer = render_primo_bw_screen;
739 set_border_geometry(256, 192);
769 renderer = render_primo_c_screen;
772 FATAL(
"Unknown primo model ID #%d",
id);
774 primo_model_set =
id;
775 primo_model_set_str = primo_model_names[
id];
776 set_title_model_details();
780 static void primo_reset (
void )
788 static void ui_cb_set_model (
const struct menu_st *m,
int *query )
796 static void ui_load_pri (
void )
798 static char fnbuf[PATH_MAX] =
"";
799 static char dir[PATH_MAX] =
"";
802 "Select PRI file to load",
807 if (pri_load(fnbuf, 0) > 0) {
816 static void primo_reset_asked (
void )
823 static void ui_cb_set_cpu_clock (
const struct menu_st *m,
int *query )
830 static void ui_sound (
const struct menu_st *m,
int *query )
837 audio_enabled = !audio_enabled;
842 static const struct menu_st menu_cpu_clock[] = {
851 static const struct menu_st menu_models[] = {
868 static const struct menu_st menu_display[] = {
877 static const struct menu_st menu_main[] = {
884 #ifdef XEMU_FILES_SCREENSHOT_SUPPORT
894 #ifdef HAVE_XEMU_EXEC_API
905 if (!pressed && pos == -2 && key == 0 && handled == SDL_BUTTON_RIGHT) {
918 int main (
int argc,
char **argv )
922 {
"rom", NULL,
"Select ROM to use", &
configdb.rom_fn },
923 {
"exprom", NULL,
"ROM expansion file selector (max 32K size)", &
configdb.exprom_fn },
924 {
"pri", NULL,
"Loads a PRI file", &
configdb.pri_name },
925 {
"model",
"b64",
"Set Primo model: a32, a48, a64, b32, b48, b64, c", &
configdb.model_name },
926 {
"gui", NULL,
"Select GUI type for usage. Specify some insane str to get a list", &
configdb.
gui_selection }
930 {
"syscon",
"Keep system console open (Windows-specific effect only)", &
configdb.
syscon },
931 {
"disasm",
"Disassemble every CPU step (uber-spammy, uber-slow!)", &
configdb.disasm },
954 for (
int a = 0; a < 0x100; a++)
955 primo_palette[a] = SDL_MapRGBA(
sdl_pix_fmt, (a >> 5) * 0xFF / 7, ((a >> 2) & 7) * 0xFF / 7, ((a << 1) & 7) * 0xFF / 7, 0xFF);
956 primo_palette_white = SDL_MapRGBA(
sdl_pix_fmt, 0xFF, 0xFF, 0xFF, 0xFF);
964 SDL_AudioSpec audio_want, audio_have;
965 SDL_memset(&audio_want, 0,
sizeof audio_want);
967 audio_want.format = AUDIO_U8;
968 audio_want.channels = 1;
969 audio_want.samples = 4096;
970 audio_want.callback = NULL;
971 audio = SDL_OpenAudioDevice(NULL, 0, &audio_want, &audio_have, 0);
972 DEBUGPRINT(
"AUDIO: dev=#%d driver=\"%s\" sampling at %d Hz" NL, audio, SDL_GetCurrentAudioDriver(), audio_have.freq);
991 if (set_model(
configdb.model_name, 1))
992 FATAL(
"Bad Primo model specified and/or ROM not found (more details: the previous message)");
1001 beeper_last_changed = 0;
1004 joy.last_clocked = 0;
1006 set_cpu_hz((
int)(
configdb.clock_mhz * 1000000.0));
1012 emu_loop_notification = 0;
1013 all_cycles_spent = 0;
1018 SDL_PauseAudioDevice(audio, 0);