Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
commodore_lcd.c
Go to the documentation of this file.
1 /* Commodore LCD emulator (son of my world's first working Commodore LCD emulator)
2  Copyright (C)2016-2021 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
3  Part of the Xemu project: https://github.com/lgblgblgb/xemu
4 
5  This is an ongoing work to rewrite my old Commodore LCD emulator:
6 
7  * Commodore LCD emulator, C version.
8  * (C)2013,2014 LGB Gabor Lenart
9  * Visit my site (the older, JavaScript version of the emu is here too): http://commodore-lcd.lgb.hu/
10 
11  The goal is - of course - writing a primitive but still better than previous Commodore LCD emulator :)
12  Note: I would be interested in VICE adoption, but I am lame with VICE, too complex for me :)
13 
14  This emulator based on my previous try (written in C), which is based on my previous JavaScript
15  based emulator, which was the world's first Commodore LCD emulator. Actually this emulator turned
16  out to be Xemu with many new machines since then to be emulated, including Commodore 65 and
17  MEGA65 too.
18 
19 This program is free software; you can redistribute it and/or modify
20 it under the terms of the GNU General Public License as published by
21 the Free Software Foundation; either version 2 of the License, or
22 (at your option) any later version.
23 
24 This program is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 GNU General Public License for more details.
28 
29 You should have received a copy of the GNU General Public License
30 along with this program; if not, write to the Free Software
31 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
32 
33 
34 #include "xemu/emutools.h"
35 #include "xemu/emutools_files.h"
36 #include "xemu/emutools_hid.h"
37 #include "xemu/emutools_config.h"
38 #include "xemu/emutools_gui.h"
39 #include "xemu/cpu65.h"
40 #include "xemu/via65c22.h"
41 #include "xemu/emutools.h"
42 
43 #include "commodore_lcd.h"
44 
45 #include <time.h>
46 
47 
48 static const char *rom_fatal_msg = "This is one of the selected ROMs. Without it, Xemu won't work.\nInstall it, or use -romXXX CLI switches to specify another path, see the -h output for help.";
49 
50 static char emulator_addon_title[32] = "";
51 
52 static int cpu_mhz, cpu_cycles_per_tv_frame;
53 static int register_screenshot_request = 0;
54 
55 static Uint8 memory[0x40000];
56 static Uint8 charrom[4096];
57 extern unsigned const char roms[];
58 static int mmu[3][4] = {
59  {0, 0, 0, 0},
60  {0, 0, 0, 0},
61  {0, 0, 0x30000, 0x30000}
62 };
63 static int *mmu_current = mmu[0];
64 static int *mmu_saved = mmu[0];
65 static Uint8 lcd_ctrl[4];
66 static struct Via65c22 via1, via2;
67 static Uint8 keysel;
68 static Uint8 rtc_regs[16];
69 static int rtc_sel = 0;
70 static int ram_size;
71 
72 static struct {
73  int phase;
74  Uint8 data[65536];
75  int size;
76 } prg_inject;
77 
78 static const Uint8 init_lcd_palette_rgb[6] = {
79  0xC0, 0xC0, 0xC0,
80  0x00, 0x00, 0x00
81 };
82 static Uint32 lcd_palette[2];
83 
84 #define VIRTUAL_SHIFT_POS 0x82
85 static const struct KeyMappingDefault lcd_key_map[] = {
86  { SDL_SCANCODE_BACKSPACE, 0x00 },
87  { SDL_SCANCODE_3, 0x01 },
88  { SDL_SCANCODE_5, 0x02 },
89  { SDL_SCANCODE_7, 0x03 },
90  { SDL_SCANCODE_9, 0x04 },
91  { SDL_SCANCODE_DOWN, 0x05 },
92  { SDL_SCANCODE_LEFT, 0x06 },
93  { SDL_SCANCODE_1, 0x07 },
94  { SDL_SCANCODE_RETURN, 0x10 },
95  { SDL_SCANCODE_W, 0x11 },
96  { SDL_SCANCODE_R, 0x12 },
97  { SDL_SCANCODE_Y, 0x13 },
98  { SDL_SCANCODE_I, 0x14 },
99  { SDL_SCANCODE_P, 0x15 },
100  { SDL_SCANCODE_RIGHTBRACKET, 0x16 }, // this would be '*'
101  { SDL_SCANCODE_HOME, 0x17 },
102  { SDL_SCANCODE_TAB, 0x20 },
103  { SDL_SCANCODE_A, 0x21 },
104  { SDL_SCANCODE_D, 0x22 },
105  { SDL_SCANCODE_G, 0x23 },
106  { SDL_SCANCODE_J, 0x24 },
107  { SDL_SCANCODE_L, 0x25 },
108  { SDL_SCANCODE_SEMICOLON, 0x26 },
109  { SDL_SCANCODE_F2, 0x27 },
110  { SDL_SCANCODE_F7, 0x30 },
111  { SDL_SCANCODE_4, 0x31 },
112  { SDL_SCANCODE_6, 0x32 },
113  { SDL_SCANCODE_8, 0x33 },
114  { SDL_SCANCODE_0, 0x34 },
115  { SDL_SCANCODE_UP, 0x35 },
116  { SDL_SCANCODE_RIGHT, 0x36 },
117  { SDL_SCANCODE_2, 0x37 },
118  { SDL_SCANCODE_F1, 0x40 },
119  { SDL_SCANCODE_Z, 0x41 },
120  { SDL_SCANCODE_C, 0x42 },
121  { SDL_SCANCODE_B, 0x43 },
122  { SDL_SCANCODE_M, 0x44 },
123  { SDL_SCANCODE_PERIOD, 0x45 },
124  { SDL_SCANCODE_ESCAPE, 0x46 },
125  { SDL_SCANCODE_SPACE, 0x47 },
126  { SDL_SCANCODE_F3, 0x50 },
127  { SDL_SCANCODE_S, 0x51 },
128  { SDL_SCANCODE_F, 0x52 },
129  { SDL_SCANCODE_H, 0x53 },
130  { SDL_SCANCODE_K, 0x54 },
131  { SDL_SCANCODE_APOSTROPHE, 0x55 }, // this would be ':'
132  { SDL_SCANCODE_EQUALS, 0x56 },
133  { SDL_SCANCODE_F8, 0x57 },
134  { SDL_SCANCODE_F5, 0x60 },
135  { SDL_SCANCODE_E, 0x61 },
136  { SDL_SCANCODE_T, 0x62 },
137  { SDL_SCANCODE_U, 0x63 },
138  { SDL_SCANCODE_O, 0x64 },
139  { SDL_SCANCODE_MINUS, 0x65 },
140  { SDL_SCANCODE_BACKSLASH, 0x66 }, // this would be '+'
141  { SDL_SCANCODE_Q, 0x67 },
142  { SDL_SCANCODE_LEFTBRACKET, 0x70 }, // this would be '@'
143  { SDL_SCANCODE_F4, 0x71 },
144  { SDL_SCANCODE_X, 0x72 },
145  { SDL_SCANCODE_V, 0x73 },
146  { SDL_SCANCODE_N, 0x74 },
147  { SDL_SCANCODE_COMMA, 0x75 },
148  { SDL_SCANCODE_SLASH, 0x76 },
149  { SDL_SCANCODE_F6, 0x77 },
150  // extra keys, not part of main keyboard map, but separated byte (SR register can provide both in CLCD)
151  { SDL_SCANCODE_END, 0x80 }, // this would be the 'STOP' key
152  { SDL_SCANCODE_CAPSLOCK, 0x81},
153  { SDL_SCANCODE_LSHIFT, 0x82 }, { SDL_SCANCODE_RSHIFT, 0x82 },
154  { SDL_SCANCODE_LCTRL, 0x83 }, { SDL_SCANCODE_RCTRL, 0x83 },
155  { SDL_SCANCODE_LALT, 0x84 }, { SDL_SCANCODE_RALT, 0x84 },
157  { 0, -1 } // this must be the last line: end of mapping table
158 };
159 
160 
161 
162 
163 int cpu65_trap_callback ( const Uint8 opcode )
164 {
165  return 0; // not recognized
166 }
167 
168 
169 void clear_emu_events ( void )
170 {
171  hid_reset_events(1);
172 }
173 
174 
175 #define GET_MEMORY(phys_addr) memory[phys_addr]
176 
177 
179  if (addr < 0x1000) return GET_MEMORY(addr);
180  if (addr < 0xF800) return GET_MEMORY((mmu_current[addr >> 14] + addr) & 0x3FFFF);
181  if (addr >= 0xFA00) return GET_MEMORY(addr | 0x30000);
182  if (addr >= 0xF980) return 0; // ACIA
183  if (addr >= 0xF900) return 0xFF; // I/O exp
184  if (addr >= 0xF880) return via_read(&via2, addr & 15);
185  return via_read(&via1, addr & 15);
186 }
187 
189  int maddr;
190  if (addr < 0x1000) {
191  memory[addr] = data;
192  return;
193  }
194  if (addr >= 0xF800) {
195  switch ((addr - 0xF800) >> 7) {
196  case 0: via_write(&via1, addr & 15, data); return;
197  case 1: via_write(&via2, addr & 15, data); return;
198  case 2: return; // I/O exp area is not handled
199  case 3: return; // no ACIA yet
200  case 4: mmu_current = mmu[2]; return;
201  case 5: mmu_current = mmu[1]; return;
202  case 6: mmu_current = mmu[0]; return;
203  case 7: mmu_current = mmu_saved; return;
204  case 8: mmu_saved = mmu_current; return;
205  case 9: FATAL("MMU test mode is set, it would not work"); break;
206  case 10: mmu[1][0] = data << 10; return;
207  case 11: mmu[1][1] = data << 10; return;
208  case 12: mmu[1][2] = data << 10; return;
209  case 13: mmu[1][3] = data << 10; return;
210  case 14: mmu[2][1] = data << 10; return;
211  case 15: lcd_ctrl[addr & 3] = data; return;
212  }
213  DEBUG("ERROR: should be not here!" NL);
214  return;
215  }
216  maddr = (mmu_current[addr >> 14] + addr) & 0x3FFFF;
217  if (maddr < ram_size) {
218  memory[maddr] = data;
219  return;
220  }
221  DEBUG("MEM: out-of-RAM write addr=$%04X maddr=$%05X" NL, addr, maddr);
222 }
223 
224 
225 // I guess Commodore LCD since used CMOS 65C02 already, no need to emulate the RMW behaviour on NMOS 6502 (??)
226 void cpu65_write_rmw_callback ( Uint16 addr, Uint8 old_data, Uint8 new_data )
227 {
228  cpu65_write_callback(addr, new_data);
229 }
230 
231 
232 static Uint8 portB1 = 0, portA2 = 0;
233 static int keytrans = 0;
234 static int powerstatus = 0;
235 
236 
237 static void via1_outa(Uint8 mask, Uint8 data) { keysel = data & mask; }
238 static void via1_outb(Uint8 mask, Uint8 data) {
239  keytrans = ((!(portB1 & 1)) && (data & 1));
240  portB1 = data;
241 }
242 static void via1_outsr(Uint8 data) {}
243 static Uint8 via1_ina(Uint8 mask) { return 0xFF; }
244 static Uint8 via1_inb(Uint8 mask) { return 0xFF; }
245 static void via2_setint(int level) {}
246 static void via2_outa(Uint8 mask, Uint8 data) {
247  portA2 = data;
248  // ugly stuff, but now the needed part is cut here from my other emulator :)
249  if (portB1 & 2) { // RTC RD
250  if (data & 64) {
251  rtc_sel = data & 15;
252  }
253  }
254 }
255 static void via2_outb(Uint8 mask, Uint8 data) {}
256 static void via2_outsr(Uint8 data) {}
257 static Uint8 via2_ina(Uint8 mask) {
258  if (portB1 & 2) {
259  if (portA2 & 16) {
260  return rtc_regs[rtc_sel] | (portA2 & 0x70);
261  }
262  return portA2;
263  }
264  return 0;
265 }
266 static Uint8 via2_inb(Uint8 mask) { return 0xFF; }
267 static Uint8 via2_insr() { return 0xFF; }
268 static Uint8 via1_insr()
269 {
270  if (keytrans) {
271  int data = 0;
272  keytrans = 0;
273  if (!(keysel & 1)) data |= ~kbd_matrix[0];
274  if (!(keysel & 2)) data |= ~kbd_matrix[1];
275  if (!(keysel & 4)) data |= ~kbd_matrix[2];
276  if (!(keysel & 8)) data |= ~kbd_matrix[3];
277  if (!(keysel & 16)) data |= ~kbd_matrix[4];
278  if (!(keysel & 32)) data |= ~kbd_matrix[5];
279  if (!(keysel & 64)) data |= ~kbd_matrix[6];
280  if (!(keysel & 128)) data |= ~kbd_matrix[7];
281  return data;
282  } else
283  return (~kbd_matrix[8]) | powerstatus;
284 }
285 static void via1_setint(int level)
286 {
287  //DEBUG("IRQ level: %d" NL, level);
288  cpu65.irqLevel = level;
289 }
290 
291 
292 
293 #define BG lcd_palette[0]
294 #define FG lcd_palette[1]
295 
296 static void render_screen ( void )
297 {
298  int ps = lcd_ctrl[1] << 7;
299  int x, y, ch;
300  int tail;
302  if (lcd_ctrl[2] & 2) { // graphic mode
303  for (y = 0; y < 128; y++) {
304  for (x = 0; x < 60; x++) {
305  ch = memory[ps++];
306  *(pix++) = (ch & 0x80) ? FG : BG;
307  *(pix++) = (ch & 0x40) ? FG : BG;
308  *(pix++) = (ch & 0x20) ? FG : BG;
309  *(pix++) = (ch & 0x10) ? FG : BG;
310  *(pix++) = (ch & 0x08) ? FG : BG;
311  *(pix++) = (ch & 0x04) ? FG : BG;
312  *(pix++) = (ch & 0x02) ? FG : BG;
313  *(pix++) = (ch & 0x01) ? FG : BG;
314  }
315  ps = (ps + 4) & 0x7FFF;
316  pix += tail;
317  }
318  } else { // text mode
319  int cof = (lcd_ctrl[2] & 1) << 10;
320  int maxx = (lcd_ctrl[3] & 4) ? 60 : 80;
321  ps += lcd_ctrl[0] & 0x7F; // X-Scroll register, only the lower 7 bits are used
322  for (y = 0; y < 128; y++) {
323  for (x = 0; x < maxx; x++) {
324  ps &= 0x7FFF;
325  ch = memory[ps++];
326  ch = charrom[cof + ((ch & 0x7F) << 3) + (y & 7)] ^ ((ch & 0x80) ? 0xFF : 0x00);
327  pix[0] = (ch & 0x80) ? FG : BG;
328  pix[1] = (ch & 0x40) ? FG : BG;
329  pix[2] = (ch & 0x20) ? FG : BG;
330  pix[3] = (ch & 0x10) ? FG : BG;
331  pix[4] = (ch & 0x08) ? FG : BG;
332  pix[5] = (ch & 0x04) ? FG : BG;
333  if (lcd_ctrl[3] & 4) {
334  pix[6] = (ch & 0x02) ? FG : BG;
335  pix[7] = (ch & 0x01) ? FG : BG;
336  pix += 8;
337  } else
338  pix += 6;
339  }
340  if ((y & 7) == 7)
341  ps += 128 - maxx;
342  else
343  ps -= maxx;
344  pix += tail;
345  }
346  }
349  if (!xemu_screenshot_png(
350  NULL, NULL,
353  NULL, // Allow function to figure it out ;)
354  SCREEN_WIDTH,
357  ))
358  OSD(-1, -1, "Screenshot has been taken");
359  }
361 }
362 
363 
364 static const struct menu_st menu_main[];
365 
366 
367 // HID needs this to be defined, it's up to the emulator if it uses or not ...
368 int emu_callback_key ( int pos, SDL_Scancode key, int pressed, int handled )
369 {
370  if (!pressed && pos == -2 && key == 0 && handled == SDL_BUTTON_RIGHT) {
371  DEBUGGUI("UI: handler has been called." NL);
372  if (xemugui_popup(menu_main))
373  DEBUGPRINT("UI: oops, POPUP does not worked :(" NL);
374  }
375  return 0;
376 }
377 
378 
379 
380 static void update_rtc ( void )
381 {
382  struct tm *t = xemu_get_localtime();
383  rtc_regs[ 0] = t->tm_sec % 10;
384  rtc_regs[ 1] = t->tm_sec / 10;
385  rtc_regs[ 2] = t->tm_min % 10;
386  rtc_regs[ 3] = t->tm_min / 10;
387  rtc_regs[ 4] = t->tm_hour % 10;
388  rtc_regs[ 5] = (t->tm_hour / 10) | 8; // TODO: AM/PM, 24h/12h time format
389  rtc_regs[ 6] = t->tm_wday; // day of week
390  rtc_regs[ 9] = t->tm_mday % 10;
391  rtc_regs[10] = t->tm_mday / 10;
392  rtc_regs[ 7] = (t->tm_mon + 1) % 10;
393  rtc_regs[ 8] = (t->tm_mon + 1) / 10;
394  rtc_regs[11] = (t->tm_year - 84) % 10; // beware of 2084, Commodore LCDs will have "Y2K-like" problem ... :)
395  rtc_regs[12] = (t->tm_year - 84) / 10;
396 }
397 
398 
399 
400 static void update_emulator ( void )
401 {
402  if (XEMU_UNLIKELY(prg_inject.phase == 1)) {
403  static const Uint8 screen_sample1[] = { 0x20, 0x03, 0x0f, 0x0d, 0x0d, 0x0f, 0x04, 0x0f, 0x12, 0x05, 0x20, 0x0c, 0x03, 0x04, 0x20 };
404  static const Uint8 screen_sample2[] = { 0x12, 0x05, 0x01, 0x04, 0x19, 0x2e };
405  if (
406  !memcmp(memory + 0x880, screen_sample1, sizeof screen_sample1) &&
407  !memcmp(memory + 0x980, screen_sample2, sizeof screen_sample2)
408  ) {
409  prg_inject.phase = 2;
410  DEBUGPRINT("BASIC: startup screen detected, injecting loaded basic program!" NL);
411  memory[0xA01] = 'R' - 'A' + 1;
412  memory[0xA02] = 'U' - 'A' + 1;
413  memory[0xA03] = 'N' - 'A' + 1;
414  memory[0x1000] = 0;
415  memset(memory + 0x1000, 0, 0x8000);
416  memcpy(memory + 0x1001, prg_inject.data + 2, prg_inject.size - 2);
417  //memset(memory + 0x1001 + prg_inject.size - 2, 0, 4);
418  int addr = 0x1001;
419  memory[0x65] = addr & 0xFF;
420  memory[0x66] = addr >> 8;
421  addr += prg_inject.size - 2;
422  memory[0x67] = addr & 0xFF;
423  memory[0x68] = addr >> 8;
424  }
425  }
427  render_screen();
429  xemu_timekeeping_delay(40000); // 40000 microseconds would be the real time for a full TV frame (see main() for more info: CLCD is not TV based for real ...)
431  update_rtc();
432 }
433 
434 
435 static void shutdown_emu ( void )
436 {
437 #if 0
438 #ifndef __EMSCRIPTEN__
439  FILE *f = fopen("memory.dump", "wb");
440  if (f) {
441  fwrite(memory, sizeof memory, 1, f);
442  fclose(f);
443  }
444 #endif
445 #endif
446  //if (keep_ram) {
447  // xemu_save_file("@memory_saved.bin", memory, ram_size, "Cannot save memory content :(");
448  //}
449  DEBUGPRINT("Shutting down ..." NL);
450 }
451 
452 
453 static void rom_list ( void )
454 {
455  //const char *defprg = xemucfg_get_str("defprg");
456  for (int addr = 0x20000; addr < 0x40000; addr += 0x4000) {
457  if (!memcmp(memory + addr + 8, "Commodore LCD", 13)) {
458  DEBUGPRINT("ROM directory entry point @ $%05X" NL, addr);
459  int pos = addr + 13 + 8;
460  while (memory[pos]) {
461  char name[256];
462  memcpy(name, memory + pos + 6, memory[pos] - 6);
463  name[memory[pos] - 6] = 0;
464  DEBUGPRINT("\t($%02X $%02X $%02X) START=$%04X : \"%s\"" NL,
465  memory[pos + 1], memory[pos + 2], memory[pos + 3],
466  memory[pos + 4] | (memory[pos + 5] <<8),
467  name
468  );
469  //if (defprg && !strcasecmp(name, defprg)) {
470  // DEBUGPRINT("\tFOUND!!!!!" NL);
471  // memory[pos + 1] |= 0x20;
472  // memory[pos + 1] = 0x20;
473  //} else if (defprg) {
474  // //memory[pos + 1] &= ~0x20;
475  //}
476  pos += memory[pos];
477  }
478  }
479  }
480 }
481 
482 
483 
484 static void load_program_for_inject ( const char *file_name, int new_address )
485 {
486  prg_inject.phase = 0;
487  if (!file_name)
488  return;
489  memset(prg_inject.data, 0, sizeof prg_inject.data);
490  prg_inject.size = xemu_load_file(file_name, prg_inject.data, 8, sizeof(prg_inject.data) - 4, "Cannot load program");
491  if (prg_inject.size <= 0)
492  return;
493  int old_address = prg_inject.data[0] | (prg_inject.data[1] << 8);
494  DEBUGPRINT("PRG: program \"%s\" load_addr=$%04X, new_load_addr=$%04X" NL, file_name, old_address, new_address);
495  if (old_address != new_address) {
496  int i = 2;
497  for (;;) {
498  if (prg_inject.data[i] == 0 && prg_inject.data[i + 1] == 0)
499  break;
500  int o = i; // offset of the next addr to patch
501  i += 4; // skip next line offset + line number
502  while (prg_inject.data[i])
503  i++;
504  i++;
505  new_address += i - o;
506  DEBUGPRINT("BASIC: re-linking line (%d) $%04X -> $%04X" NL,
507  prg_inject.data[o + 2] | (prg_inject.data[o + 3] << 8),
508  prg_inject.data[o] | (prg_inject.data[o + 1] << 8),
509  new_address
510  );
511  prg_inject.data[o] = new_address & 0xFF;
512  prg_inject.data[o + 1] = new_address >> 8;
513  }
514 
515  }
516  prg_inject.phase = 1;
517 }
518 
519 
520 static void update_addon_title ( void )
521 {
522  sprintf(emulator_addon_title, "(%dMHz, %dK RAM)", cpu_mhz, ram_size >> 10);
523 }
524 
525 
526 static void set_ram_size ( int kbytes )
527 {
528  ram_size = kbytes << 10;
529  DEBUGPRINT("MEM: RAM size is set to %dKbytes." NL, kbytes);
530  update_addon_title();
531 }
532 
533 static void set_cpu_speed ( int mhz )
534 {
535  cpu_mhz = mhz;
536  cpu_cycles_per_tv_frame = mhz * 1000000 / 25;
537  DEBUGPRINT("CPU: setting CPU to %dMHz, %d CPU cycles per full 1/25sec frame." NL, mhz, cpu_cycles_per_tv_frame);
538  update_addon_title();
539 }
540 
541 
542 
543 static int cycles, viacyc;
544 
545 
546 static void emulation_loop ( void )
547 {
548  for (;;) {
549  int opcyc = cpu65_step(); // execute one opcode (or accept IRQ, etc), return value is the used clock cycles
550  viacyc += opcyc;
551  cycles += opcyc;
552  if (viacyc >= cpu_mhz) {
553  int steps = viacyc / cpu_mhz;
554  viacyc = viacyc % cpu_mhz;
555  via_tick(&via1, steps); // run VIA-1 tasks for the same amount of cycles as the CPU would do @ 1MHz
556  via_tick(&via2, steps); // -- "" -- the same for VIA-2
557  }
558  /* Note, Commodore LCD is not TV standard based ... Since I have no idea about the update etc, I still assume some kind of TV-related stuff, who cares :) */
559  if (XEMU_UNLIKELY(cycles >= cpu_cycles_per_tv_frame)) { // if enough cycles elapsed (what would be the amount of CPU cycles for a TV frame), let's call the update function.
560  update_emulator(); // this is the heart of screen update, also to handle SDL events (like key presses ...)
561  cycles -= cpu_cycles_per_tv_frame; // not just cycle = 0, to avoid rounding errors, but it would not matter too much anyway ...
562  return;
563  }
564  }
565 }
566 
567 
568 
569 
570 static void ui_cb_clock_mhz ( const struct menu_st *m, int *query )
571 {
573  if (VOIDPTR_TO_INT(m->user_data) != cpu_mhz)
574  set_cpu_speed(VOIDPTR_TO_INT(m->user_data));
575 }
576 static void ui_cb_ramsize ( const struct menu_st *m, int *query )
577 {
578  XEMUGUI_RETURN_CHECKED_ON_QUERY(query, (ram_size >> 10) == VOIDPTR_TO_INT(m->user_data));
579  if (VOIDPTR_TO_INT(m->user_data) != (ram_size >> 10) && ARE_YOU_SURE("This will RESET your machine!", i_am_sure_override | ARE_YOU_SURE_DEFAULT_YES)) {
580  set_ram_size(VOIDPTR_TO_INT(m->user_data));
581  cpu65_reset();
582  }
583 }
584 static const struct menu_st menu_display[] = {
585  { "Fullscreen", XEMUGUI_MENUID_CALLABLE, xemugui_cb_windowsize, (void*)0 },
586  { "Window - 100%", XEMUGUI_MENUID_CALLABLE, xemugui_cb_windowsize, (void*)1 },
587  { "Window - 200%", XEMUGUI_MENUID_CALLABLE |
589  { NULL }
590 };
591 static const struct menu_st menu_clock[] = {
592  { "1MHz", XEMUGUI_MENUID_CALLABLE |
593  XEMUGUI_MENUFLAG_QUERYBACK, ui_cb_clock_mhz, (void*)1 },
594  { "2MHz", XEMUGUI_MENUID_CALLABLE |
595  XEMUGUI_MENUFLAG_QUERYBACK, ui_cb_clock_mhz, (void*)2 },
596  { "4MHz", XEMUGUI_MENUID_CALLABLE |
597  XEMUGUI_MENUFLAG_QUERYBACK, ui_cb_clock_mhz, (void*)4 },
598  { NULL }
599 };
600 static const struct menu_st menu_ramsize[] = {
601  { " 32K", XEMUGUI_MENUID_CALLABLE |
602  XEMUGUI_MENUFLAG_QUERYBACK, ui_cb_ramsize, (void*)32 },
603  { " 64K", XEMUGUI_MENUID_CALLABLE |
604  XEMUGUI_MENUFLAG_QUERYBACK, ui_cb_ramsize, (void*)64 },
605  { " 96K", XEMUGUI_MENUID_CALLABLE |
606  XEMUGUI_MENUFLAG_QUERYBACK, ui_cb_ramsize, (void*)96 },
607  { "128K", XEMUGUI_MENUID_CALLABLE |
608  XEMUGUI_MENUFLAG_QUERYBACK, ui_cb_ramsize, (void*)128 },
609  { NULL }
610 };
611 static const struct menu_st menu_main[] = {
612  { "Display", XEMUGUI_MENUID_SUBMENU, NULL, menu_display },
613  { "CPU clock speed", XEMUGUI_MENUID_SUBMENU, NULL, menu_clock },
614  { "RAM size", XEMUGUI_MENUID_SUBMENU, NULL, menu_ramsize },
615 #ifdef XEMU_FILES_SCREENSHOT_SUPPORT
617 #endif
618 #ifdef XEMU_ARCH_WIN
619  { "System console", XEMUGUI_MENUID_CALLABLE |
620  XEMUGUI_MENUFLAG_QUERYBACK, xemugui_cb_sysconsole, NULL },
621 #endif
622 #ifdef HAVE_XEMU_EXEC_API
623  { "Browse system folder", XEMUGUI_MENUID_CALLABLE, xemugui_cb_native_os_prefdir_browser, NULL },
624 #endif
627 #ifdef HAVE_XEMU_EXEC_API
628  { "Help (on-line)", XEMUGUI_MENUID_CALLABLE, xemugui_cb_web_help_main, NULL },
629 #endif
631  { NULL }
632 };
633 
634 
635 static struct {
637  int zoom;
641  char *rom102_fn;
642  char *rom103_fn;
643  char *rom104_fn;
644  char *rom105_fn;
645  char *romchr_fn;
648 } configdb;
649 
650 
651 
652 int main ( int argc, char **argv )
653 {
654  xemu_pre_init(APP_ORG, TARGET_NAME, "The world's first Commodore LCD emulator from LGB");
656  { "fullscreen", "Start in fullscreen mode", &configdb.fullscreen_requested },
657  { "keepram", "Deactivate ROM patch for clear RAM and also save/restore RAM", &configdb.keep_ram },
658  { "syscon", "Keep system console open (Windows-specific effect only)", &configdb.syscon },
659  { "besure", "Skip asking \"are you sure?\" on RESET or EXIT", &i_am_sure_override }
660  );
662 #ifdef SDL_HINT_RENDER_SCALE_QUALITY
663  { "sdlrenderquality", RENDER_SCALE_QUALITY, "Setting SDL hint for scaling method/quality on rendering (0, 1, 2)", &configdb.sdlrenderquality, 0, 2 },
664 #endif
665  { "zoom", 1, "Window zoom value (1-4)", &configdb.zoom, 1, 4 },
666  { "ram", 128, "Sets RAM size in KBytes (32-128)", &configdb.ram_size_kbytes, 32, 128 },
667  { "clock", 1, "Sets CPU speed in MHz (integer only) 1-16", &configdb.clock_mhz, 1, 16 }
668  );
670  { "rom102", "#clcd-u102.rom", "Selects 'U102' ROM to use", &configdb.rom102_fn },
671  { "rom103", "#clcd-u103.rom", "Selects 'U103' ROM to use", &configdb.rom103_fn },
672  { "rom104", "#clcd-u104.rom", "Selects 'U104' ROM to use", &configdb.rom104_fn },
673  { "rom105", "#clcd-u105.rom", "Selects 'U105' ROM to use", &configdb.rom105_fn },
674  { "romchr", "#clcd-chargen.rom", "Selects character ROM to use", &configdb.romchr_fn },
675  //{ "defprg", NULL, "Selects the ROM-program to set default to", &configdb.defprg },
676  { "prg", NULL, "Inject BASIC program on entering to BASIC", &configdb.prg_inject_fn },
677  { "gui", NULL, "Select GUI type for usage. Specify some insane str to get a list", &configdb.gui_selection }
678  );
679  if (xemucfg_parse_all(argc, argv))
680  return 1;
681  //xemucfg_limit_num(&configdb.sdlrenderquality, 0, 2);
682  //xemucfg_limit_num(&configdb.clock_mhz, 1, 16);
683  //xemucfg_limit_num(&configdb.ram_size_kbytes, 32, 128);
684  set_cpu_speed(configdb.clock_mhz);
685  set_ram_size(configdb.ram_size_kbytes);
686  DEBUGPRINT("CFG: ram size is %d Kbytes." NL, ram_size);
687  window_title_info_addon = emulator_addon_title;
688  if (xemu_post_init(
689  TARGET_DESC APP_DESC_APPEND, // window title
690  1, // resizable window
691  SCREEN_WIDTH, SCREEN_HEIGHT, // texture sizes
692  SCREEN_WIDTH, SCREEN_HEIGHT, // logical size (same as texture for now ...)
694  SCREEN_FORMAT, // pixel format
695  2, // we have 2 colours :)
696  init_lcd_palette_rgb, // initialize palette from this constant array
697  lcd_palette, // initialize palette into this stuff
698 #ifdef SDL_HINT_RENDER_SCALE_QUALITY
699  configdb.sdlrenderquality,// render scaling quality
700 #else
701  RENDER_SCALE_QUALITY, // render scaling quality
702 #endif
703  USE_LOCKED_TEXTURE, // 1 = locked texture access
704  shutdown_emu // no emulator specific shutdown function
705  ))
706  return 1;
708  xemugui_init(configdb.gui_selection); // allow to fail (do not exit if it fails). Some targets may not have X running
709  hid_init(
710  lcd_key_map,
712  SDL_DISABLE // no joystick HID events enabled
713  );
714  memset(memory, 0xFF, sizeof memory);
715  memset(charrom, 0xFF, sizeof charrom);
716  if (
717  xemu_load_file(configdb.rom102_fn, memory + 0x38000, 0x8000, 0x8000, rom_fatal_msg) < 0 ||
718  xemu_load_file(configdb.rom103_fn, memory + 0x30000, 0x8000, 0x8000, rom_fatal_msg) < 0 ||
719  xemu_load_file(configdb.rom104_fn, memory + 0x28000, 0x8000, 0x8000, rom_fatal_msg) < 0 ||
720  xemu_load_file(configdb.rom105_fn, memory + 0x20000, 0x8000, 0x8000, rom_fatal_msg) < 0 ||
721  xemu_load_file(configdb.romchr_fn, charrom, 0x1000, 0x1000, rom_fatal_msg) < 0
722  )
723  return 1;
724  // Ugly hacks :-( <patching ROM>
725 #ifdef ROM_HACK_COLD_START
726  if (!configdb.keep_ram) {
727  // this ROM patching is needed, as Commodore LCD seems not to work to well with "not intact" SRAM content (ie: it has battery powered SRAM even when "switched off")
728  DEBUGPRINT("ROM-HACK: forcing cold start condition with ROM patching!" NL);
729  memory[0x385BB] = 0xEA;
730  memory[0x385BC] = 0xEA;
731  } else {
732  DEBUGPRINT("ROM-HACK: SKIP cold start condition forcing!" NL);
733  xemu_load_file("@memory_saved.bin", memory, ram_size, ram_size, "Cannot load memory content!");
734  }
735 #endif
736 #ifdef ROM_HACK_NEW_ROM_SEARCHING
737  // this ROM hack modifies the ROM signature searching bytes so we can squeeze extra menu points of the main screen!
738  // this hack SHOULD NOT be used, if the ROM 32K ROM images from 0x20000 and 0x28000 are not empty after offset 0x6800
739  // WARNING: Commodore LCDs are known to have different ROM versions, be careful with different ROMs, if you find any!
740  // [note: if you find other ROM versions, please tell me!!!! - that's the other message ...]
741  DEBUG("ROM HACK: modifying ROM searching MMU table" NL);
742  // overwrite MMU table positions for ROM scanner in KERNAL
743  memory[0x382CC] = 0x8A; // offset 0x6800 in the ROM image of clcd-u105.rom [phys memory address: 0x26800]
744  memory[0x382CE] = 0xAA; // offset 0x6800 in the ROM image of clcd-u104.rom [phys memory address: 0x2E800]
745  // try to load "parasite" ROMs (it's not fatal if we cannot ...)
746  // these loads to an unused part of the original ROM images
747  xemu_load_file("#clcd-u105-parasite.rom", memory + 0x26800, 32, 0x8000 - 0x6800, NULL);
748  xemu_load_file("#clcd-u104-parasite.rom", memory + 0x2E800, 32, 0x8000 - 0x6800, NULL);
749 #endif
750  load_program_for_inject(configdb.prg_inject_fn, 0x1001);
751  rom_list();
752  /* init CPU */
753  cpu65_reset(); // we must do this after loading KERNAL at least, since PC is fetched from reset vector here!
754  /* init VIAs */
755  via_init(&via1, "VIA#1", via1_outa, via1_outb, via1_outsr, via1_ina, via1_inb, via1_insr, via1_setint);
756  via_init(&via2, "VIA#2", via2_outa, via2_outb, via2_outsr, via2_ina, via2_inb, via2_insr, via2_setint);
757  /* keyboard */
758  clear_emu_events(); // also resets the keyboard
759  keysel = 0;
760  /* --- START EMULATION --- */
761  cycles = 0;
763  if (!configdb.syscon)
764  sysconsole_close(NULL);
765  xemu_timekeeping_start(); // we must call this once, right before the start of the emulation
766  update_rtc(); // this will use time-keeping stuff as well, so initially let's do after the function call above
767  viacyc = 0;
768  // FIXME: add here the "OK to save ROM state" ...
769  XEMU_MAIN_LOOP(emulation_loop, 25, 1);
770  return 0;
771 }
xemu_pre_init
void xemu_pre_init(const char *app_organization, const char *app_name, const char *slogan)
Definition: emutools.c:651
fullscreen_requested
int fullscreen_requested
Definition: commodore_lcd.c:636
USE_LOCKED_TEXTURE
#define USE_LOCKED_TEXTURE
Definition: commodore_65.h:26
via_tick
void via_tick(struct Via65c22 *via, int ticks)
Definition: via65c22.c:224
ram_size_kbytes
int ram_size_kbytes
Definition: commodore_lcd.c:639
TARGET_DESC
#define TARGET_DESC
Definition: xemu-target.h:2
romchr_fn
char * romchr_fn
Definition: commodore_lcd.c:645
XEMUCFG_DEFINE_SWITCH_OPTIONS
#define XEMUCFG_DEFINE_SWITCH_OPTIONS(...)
Definition: emutools_config.h:123
seconds_timer_trigger
int seconds_timer_trigger
Definition: emutools.c:101
BG
#define BG
Definition: commodore_lcd.c:293
menu_st
Definition: emutools_gui.h:65
configdb_st::fullscreen_requested
int fullscreen_requested
Definition: configdb.h:35
xemu_timekeeping_delay
void xemu_timekeeping_delay(int td_em)
Definition: emutools.c:405
emutools.h
sysconsole_close
void sysconsole_close(const char *waitmsg)
Definition: emutools.c:1393
XEMU_MAIN_LOOP
#define XEMU_MAIN_LOOP(func, p1, p2)
Definition: emutools.h:58
xemu_get_localtime
struct tm * xemu_get_localtime(void)
Definition: emutools.c:187
data
Uint8 data[65536]
Definition: commodore_lcd.c:74
cpu65_write_callback
void cpu65_write_callback(Uint16 addr, Uint8 data)
Definition: commodore_lcd.c:188
SCREEN_DEFAULT_ZOOM
#define SCREEN_DEFAULT_ZOOM
Definition: commodore_lcd.h:24
menu_st::user_data
const void * user_data
Definition: emutools_gui.h:69
SCREEN_WIDTH
#define SCREEN_WIDTH
Definition: vic3.h:29
xemugui_popup
int xemugui_popup(const struct menu_st desc[])
Definition: emutools_gui.c:135
register_screenshot_request
int register_screenshot_request
Definition: enterprise128.c:54
xemu_update_screen
void xemu_update_screen(void)
Definition: emutools.c:1184
via65c22.h
i_am_sure_override
int i_am_sure_override
Definition: emutools.c:74
rom105_fn
char * rom105_fn
Definition: commodore_lcd.c:644
addr
int addr
Definition: dma65.c:81
hid_handle_all_sdl_events
void hid_handle_all_sdl_events(void)
Definition: emutools_hid.c:613
xemucfg_parse_all
int xemucfg_parse_all(int argc, char **argv)
XEMUGUI_MENUID_CALLABLE
#define XEMUGUI_MENUID_CALLABLE
Definition: emutools_gui.h:30
emutools_gui.h
osd_init_with_defaults
int osd_init_with_defaults(void)
Definition: osd.c:131
Uint32
uint32_t Uint32
Definition: fat32.c:49
hid_init
void hid_init(const struct KeyMappingDefault *key_map_in, Uint8 virtual_shift_pos_in, int joy_enable)
Definition: emutools_hid.c:300
xemugui_cb_call_user_data_if_sure
void xemugui_cb_call_user_data_if_sure(const struct menu_st *m, int *query)
Definition: popular_user_funcs.c:35
Via65c22
Definition: via65c22.h:21
hid_reset_events
void hid_reset_events(int burn)
Definition: emutools_hid.c:170
Uint8
uint8_t Uint8
Definition: fat32.c:51
zoom
int zoom
Definition: commodore_lcd.c:637
xemu_set_full_screen
void xemu_set_full_screen(int setting)
Definition: emutools.c:311
cpu65_write_rmw_callback
void cpu65_write_rmw_callback(Uint16 addr, Uint8 old_data, Uint8 new_data)
Definition: commodore_lcd.c:226
configdb_st::sdlrenderquality
int sdlrenderquality
Definition: configdb.h:35
xemugui_init
int xemugui_init(const char *name)
Definition: emutools_gui.c:82
clear_emu_events
void clear_emu_events(void)
Definition: commodore_lcd.c:169
prg_inject_fn
char * prg_inject_fn
Definition: commodore_lcd.c:646
XEMUGUI_MENUFLAG_QUERYBACK
#define XEMUGUI_MENUFLAG_QUERYBACK
Definition: emutools_gui.h:40
FG
#define FG
Definition: commodore_lcd.c:294
TARGET_NAME
#define TARGET_NAME
Definition: xemu-target.h:1
emutools_files.h
clock_mhz
int clock_mhz
Definition: commodore_lcd.c:640
APP_ORG
#define APP_ORG
Definition: emutools.h:50
x
int x
Definition: console.c:27
STD_XEMU_SPECIAL_KEYS
#define STD_XEMU_SPECIAL_KEYS
Definition: emutools_hid.h:108
via_init
void via_init(struct Via65c22 *via, const char *name, void(*outa)(Uint8 mask, Uint8 data), void(*outb)(Uint8 mask, Uint8 data), void(*outsr)(Uint8 data), Uint8(*ina)(Uint8 mask), Uint8(*inb)(Uint8 mask), Uint8(*insr)(void), void(*setint)(int level))
Definition: via65c22.c:77
DEBUGPRINT
#define DEBUGPRINT(...)
Definition: emutools_basicdefs.h:171
VOIDPTR_TO_INT
#define VOIDPTR_TO_INT(x)
Definition: emutools_basicdefs.h:270
cpu65_trap_callback
int cpu65_trap_callback(const Uint8 opcode)
Definition: commodore_lcd.c:163
keep_ram
int keep_ram
Definition: commodore_lcd.c:636
VIRTUAL_SHIFT_POS
#define VIRTUAL_SHIFT_POS
Definition: commodore_lcd.c:84
xemu_start_pixel_buffer_access
Uint32 * xemu_start_pixel_buffer_access(int *texture_tail)
Definition: emutools.c:1153
size
int size
Definition: commodore_lcd.c:75
NL
#define NL
Definition: fat32.c:37
emutools_config.h
xemugui_cb_set_integer_to_one
void xemugui_cb_set_integer_to_one(const struct menu_st *m, int *query)
Definition: popular_user_funcs.c:187
configdb
struct configdb_st configdb
Definition: configdb.c:34
KeyMappingDefault
Definition: emutools_hid.h:24
xemu_post_init
int xemu_post_init(const char *window_title, int is_resizable, int texture_x_size, int texture_y_size, int logical_x_size, int logical_y_size, int win_x_size, int win_y_size, Uint32 pixel_format, int n_colours, const Uint8 *colours, Uint32 *store_palette, int render_scale_quality, int locked_texture_update, void(*shutdown_callback)(void))
Definition: emutools.c:908
memory
Uint8 memory[0x100000]
Definition: commodore_65.c:43
xemu_load_file
int xemu_load_file(const char *filename, void *store_to, int min_size, int max_size, const char *cry)
Definition: emutools_files.c:674
phase
int phase
Definition: commodore_lcd.c:73
cpu65.h
sdlrenderquality
int sdlrenderquality
Definition: commodore_lcd.c:638
ARE_YOU_SURE_DEFAULT_YES
#define ARE_YOU_SURE_DEFAULT_YES
Definition: emutools.h:125
xemu_timekeeping_start
void xemu_timekeeping_start(void)
Definition: emutools.c:1122
via_read
Uint8 via_read(struct Via65c22 *via, int addr)
Definition: via65c22.c:177
rom104_fn
char * rom104_fn
Definition: commodore_lcd.c:643
configdb_st::syscon
int syscon
Definition: configdb.h:34
rom102_fn
char * rom102_fn
Definition: commodore_lcd.c:641
kbd_matrix
Uint8 kbd_matrix[16]
Definition: dave.c:30
cpu65_reset
void cpu65_reset(void)
Definition: cpu65.c:353
ARE_YOU_SURE
int ARE_YOU_SURE(const char *s, int flags)
Definition: emutools.c:1202
SCREEN_HEIGHT
#define SCREEN_HEIGHT
Definition: vic3.h:30
cpu65_read_callback
Uint8 cpu65_read_callback(Uint16 addr)
Definition: commodore_lcd.c:178
SCREEN_FORMAT
#define SCREEN_FORMAT
Definition: commodore_65.h:25
OSD
#define OSD(...)
Definition: xep128.h:100
XEMUCFG_DEFINE_STR_OPTIONS
#define XEMUCFG_DEFINE_STR_OPTIONS(...)
Definition: emutools_config.h:119
RENDER_SCALE_QUALITY
#define RENDER_SCALE_QUALITY
Definition: commodore_65.h:27
XEMUGUI_MENUID_SUBMENU
#define XEMUGUI_MENUID_SUBMENU
Definition: emutools_gui.h:31
XEMUCFG_DEFINE_NUM_OPTIONS
#define XEMUCFG_DEFINE_NUM_OPTIONS(...)
Definition: emutools_config.h:120
y
int y
Definition: console.c:27
gui_selection
char * gui_selection
Definition: commodore_lcd.c:647
mask
int mask
Definition: dma65.c:83
GET_MEMORY
#define GET_MEMORY(phys_addr)
Definition: commodore_lcd.c:175
XEMUGUI_RETURN_CHECKED_ON_QUERY
#define XEMUGUI_RETURN_CHECKED_ON_QUERY(query, status)
Definition: emutools_gui.h:55
APP_DESC_APPEND
#define APP_DESC_APPEND
Definition: emutools.h:52
Uint16
uint16_t Uint16
Definition: fat32.c:50
xemugui_iteration
int xemugui_iteration(void)
Definition: emutools_gui.c:120
xemugui_cb_call_quit_if_sure
void xemugui_cb_call_quit_if_sure(const struct menu_st *m, int *query)
Definition: popular_user_funcs.c:79
emutools_hid.h
via_write
void via_write(struct Via65c22 *via, int addr, Uint8 data)
Definition: via65c22.c:99
rom103_fn
char * rom103_fn
Definition: commodore_lcd.c:642
xemugui_cb_windowsize
void xemugui_cb_windowsize(const struct menu_st *m, int *query)
Definition: popular_user_funcs.c:97
name
const char * name
Definition: joystick.c:46
main
int main(int argc, char **argv)
Definition: commodore_lcd.c:652
DEBUG
#define DEBUG(...)
Definition: emutools_basicdefs.h:167
syscon
int syscon
Definition: commodore_lcd.c:636
configdb_st::gui_selection
char * gui_selection
Definition: configdb.h:43
cpu65_step
int cpu65_step(void)
Definition: cpu65.c:796
xemugui_cb_about_window
void xemugui_cb_about_window(const struct menu_st *m, int *query)
Definition: popular_user_funcs.c:53
FATAL
#define FATAL(...)
Definition: xep128.h:117
window_title_info_addon
char * window_title_info_addon
Definition: emutools.c:91
emu_callback_key
int emu_callback_key(int pos, SDL_Scancode key, int pressed, int handled)
Definition: commodore_lcd.c:368
XEMU_UNLIKELY
#define XEMU_UNLIKELY(__x__)
Definition: emutools_basicdefs.h:125
DEBUGGUI
#define DEBUGGUI
Definition: emutools_gui.h:22
XEMUGUI_MENUFLAG_SEPARATOR
#define XEMUGUI_MENUFLAG_SEPARATOR
Definition: emutools_gui.h:38
commodore_lcd.h
roms
const unsigned char roms[]