comparison coleco.c @ 2415:23052186705a

Forgot to commit the colecovision files
author Michael Pavone <pavone@retrodev.com>
date Thu, 04 Jan 2024 23:46:32 -0800
parents
children
comparison
equal deleted inserted replaced
2414:dc05f1805921 2415:23052186705a
1 #include <string.h>
2 #include <stdlib.h>
3 #include <stddef.h>
4 #include "coleco.h"
5 #include "render.h"
6 #include "io.h"
7 #include "blastem.h"
8 #include "util.h"
9 #include "debug.h"
10 #include "bindings.h"
11 #include "saves.h"
12
13 #ifdef NEW_CORE
14 #define Z80_CYCLE cycles
15 #define Z80_OPTS opts
16 #define z80_handle_code_write(...)
17 #else
18 #define Z80_CYCLE current_cycle
19 #define Z80_OPTS options
20 #endif
21
22 static void *coleco_select_write(uint32_t location, void *vcontext, uint8_t value)
23 {
24 z80_context *z80 = vcontext;
25 coleco_context *coleco = z80->system;
26 location &= 0xFF;
27 coleco->controller_select = location >= 0xC0;
28 return vcontext;
29 }
30
31 static uint8_t coleco_controller_read(uint32_t location, void *vcontext)
32 {
33 z80_context *z80 = vcontext;
34 coleco_context *coleco = z80->system;
35
36 uint8_t index = coleco->controller_select ? 2 : 0;
37 if (location & 2) {
38 ++index;
39 }
40 return coleco->controller_state[index];
41 }
42
43 static void update_interrupts(coleco_context *coleco)
44 {
45 #ifdef NEW_CORE
46 if (coleco->z80->nmi_cycle == CYCLE_NEVER) {
47 #else
48 if (coleco->z80->nmi_start == CYCLE_NEVER) {
49 #endif
50 uint32_t nmi = vdp_next_vint(coleco->vdp);
51 if (nmi != CYCLE_NEVER) {
52 z80_assert_nmi(coleco->z80, nmi);
53 }
54 }
55 }
56
57 static void *coleco_vdp_write(uint32_t location, void *vcontext, uint8_t value)
58 {
59 z80_context *z80 = vcontext;
60 coleco_context *coleco = z80->system;
61
62 vdp_run_context_full(coleco->vdp, z80->Z80_CYCLE);
63 if (location & 1) {
64 vdp_control_port_write_pbc(coleco->vdp, value);
65 update_interrupts(coleco);
66 } else {
67 vdp_data_port_write_pbc(coleco->vdp, value);
68 }
69
70 return vcontext;
71 }
72
73 static uint8_t coleco_vdp_read(uint32_t location, void *vcontext)
74 {
75 z80_context *z80 = vcontext;
76 coleco_context *coleco = z80->system;
77 vdp_run_context(coleco->vdp, z80->Z80_CYCLE);
78 if (location & 1) {
79 uint8_t ret = vdp_control_port_read(coleco->vdp);
80 coleco->vdp->flags2 &= ~(FLAG2_VINT_PENDING|FLAG2_HINT_PENDING);
81 update_interrupts(coleco);
82 return ret;
83 } else {
84 return vdp_data_port_read_pbc(coleco->vdp);
85 }
86 }
87
88 static void *coleco_psg_write(uint32_t location, void *vcontext, uint8_t value)
89 {
90 z80_context *z80 = vcontext;
91 coleco_context *coleco = z80->system;
92
93 psg_run(coleco->psg, z80->Z80_CYCLE);
94 psg_write(coleco->psg, value);
95
96 return vcontext;
97 }
98
99 void coleco_serialize(coleco_context *coleco, serialize_buffer *buf)
100 {
101 start_section(buf, SECTION_Z80);
102 z80_serialize(coleco->z80, buf);
103 end_section(buf);
104
105 start_section(buf, SECTION_VDP);
106 vdp_serialize(coleco->vdp, buf);
107 end_section(buf);
108
109 start_section(buf, SECTION_PSG);
110 psg_serialize(coleco->psg, buf);
111 end_section(buf);
112
113 start_section(buf, SECTION_MAIN_RAM);
114 save_int8(buf, sizeof(coleco->ram) / 1024);
115 save_buffer8(buf, coleco->ram, sizeof(coleco->ram));
116 end_section(buf);
117
118 start_section(buf, SECTION_COLECO_IO);
119 save_buffer8(buf, coleco->controller_state, sizeof(coleco->controller_state));
120 save_int8(buf, coleco->controller_select);
121 end_section(buf);
122 }
123
124 static uint8_t *serialize(system_header *sys, size_t *size_out)
125 {
126 coleco_context *coleco = (coleco_context *)sys;
127 serialize_buffer state;
128 init_serialize(&state);
129 coleco_serialize(coleco, &state);
130 if (size_out) {
131 *size_out = state.size;
132 }
133 return state.data;
134 }
135
136 static void ram_deserialize(deserialize_buffer *buf, void *vcoleco)
137 {
138 coleco_context *coleco = vcoleco;
139 uint32_t ram_size = load_int8(buf) * 1024;
140 if (ram_size > sizeof(coleco->ram)) {
141 fatal_error("State has a RAM size of %d bytes", ram_size);
142 }
143 load_buffer8(buf, coleco->ram, ram_size);
144 }
145
146 static void controller_deserialize(deserialize_buffer *buf, void *vcoleco)
147 {
148 coleco_context *coleco = vcoleco;
149 load_buffer8(buf, coleco->controller_state, sizeof(coleco->controller_state));
150 coleco->controller_select = load_int8(buf);
151 }
152
153 void coleco_deserialize(deserialize_buffer *buf, coleco_context *coleco)
154 {
155 register_section_handler(buf, (section_handler){.fun = z80_deserialize, .data = coleco->z80}, SECTION_Z80);
156 register_section_handler(buf, (section_handler){.fun = vdp_deserialize, .data = coleco->vdp}, SECTION_VDP);
157 register_section_handler(buf, (section_handler){.fun = psg_deserialize, .data = coleco->psg}, SECTION_PSG);
158 register_section_handler(buf, (section_handler){.fun = ram_deserialize, .data = coleco}, SECTION_MAIN_RAM);
159 register_section_handler(buf, (section_handler){.fun = controller_deserialize, .data = coleco}, SECTION_COLECO_IO);
160 while (buf->cur_pos < buf->size)
161 {
162 load_section(buf);
163 }
164 z80_invalidate_code_range(coleco->z80, 0x7000, 0x8000);
165 free(buf->handlers);
166 buf->handlers = NULL;
167 }
168
169 static void deserialize(system_header *sys, uint8_t *data, size_t size)
170 {
171 coleco_context *coleco = (coleco_context *)sys;
172 deserialize_buffer buffer;
173 init_deserialize(&buffer, data, size);
174 coleco_deserialize(&buffer, coleco);
175 }
176
177 static void save_state(coleco_context *coleco, uint8_t slot)
178 {
179 char *save_path = get_slot_name(&coleco->header, slot, "state");
180 serialize_buffer state;
181 init_serialize(&state);
182 coleco_serialize(coleco, &state);
183 save_to_file(&state, save_path);
184 printf("Saved state to %s\n", save_path);
185 free(save_path);
186 free(state.data);
187 }
188
189 static uint8_t load_state_path(coleco_context *coleco, char *path)
190 {
191 deserialize_buffer state;
192 uint8_t ret;
193 if ((ret = load_from_file(&state, path))) {
194 coleco_deserialize(&state, coleco);
195 free(state.data);
196 printf("Loaded %s\n", path);
197 }
198 return ret;
199 }
200
201 static uint8_t load_state(system_header *system, uint8_t slot)
202 {
203 coleco_context *coleco = (coleco_context *)system;
204 char *statepath = get_slot_name(system, slot, "state");
205 uint8_t ret;
206 #ifndef NEW_CORE
207 if (!coleco->z80->native_pc) {
208 ret = get_modification_time(statepath) != 0;
209 if (ret) {
210 system->delayed_load_slot = slot + 1;
211 }
212 goto done;
213
214 }
215 #endif
216 ret = load_state_path(coleco, statepath);
217 done:
218 free(statepath);
219 return ret;
220 }
221
222 static void run_coleco(system_header *system)
223 {
224 coleco_context *coleco = (coleco_context *)system;
225 uint32_t target_cycle = coleco->z80->Z80_CYCLE + 3420*16;
226 render_set_video_standard(VID_NTSC);
227
228 while (!coleco->should_return) {
229 if (system->delayed_load_slot) {
230 load_state(system, system->delayed_load_slot - 1);
231 system->delayed_load_slot = 0;
232
233 }
234 if (coleco->vdp->frame != coleco->last_frame) {
235 #ifndef IS_LIB
236 if (coleco->psg->scope) {
237 scope_render(coleco->psg->scope);
238 }
239 #endif
240 uint32_t elapsed = coleco->vdp->frame - coleco->last_frame;
241 coleco->last_frame = coleco->vdp->frame;
242 if (system->enter_debugger_frames) {
243 if (elapsed >= system->enter_debugger_frames) {
244 system->enter_debugger_frames = 0;
245 system->enter_debugger = 1;
246 } else {
247 system->enter_debugger_frames -= elapsed;
248 }
249 }
250
251 if(exit_after){
252 if (elapsed >= exit_after) {
253 exit(0);
254 } else {
255 exit_after -= elapsed;
256 }
257 }
258 }
259 if (system->enter_debugger && coleco->z80->pc) {
260 system->enter_debugger = 0;
261 #ifndef IS_LIB
262 zdebugger(coleco->z80, coleco->z80->pc);
263 #endif
264 }
265 if (system->enter_debugger) {
266 target_cycle = coleco->z80->Z80_CYCLE + 1;
267 }
268 update_interrupts(coleco);
269 z80_run(coleco->z80, target_cycle);
270 if (coleco->z80->reset) {
271 z80_clear_reset(coleco->z80, coleco->z80->Z80_CYCLE + 3*15);
272 }
273 target_cycle = coleco->z80->Z80_CYCLE;
274 vdp_run_context(coleco->vdp, target_cycle);
275 psg_run(coleco->psg, target_cycle);
276 if (system->save_state) {
277 while (!coleco->z80->pc) {
278 //advance Z80 to an instruction boundary
279 z80_run(coleco->z80, coleco->z80->Z80_CYCLE + 1);
280 }
281 save_state(coleco, system->save_state - 1);
282 system->save_state = 0;
283 }
284
285 target_cycle += 3420*16;
286 if (target_cycle > 0x40000000) {
287 uint32_t adjust = coleco->z80->Z80_CYCLE - 3420*262*2;
288 z80_adjust_cycles(coleco->z80, adjust);
289 vdp_adjust_cycles(coleco->vdp, adjust);
290 coleco->psg->cycles -= adjust;
291 target_cycle -= adjust;
292 }
293 }
294 if (coleco->header.force_release || render_should_release_on_exit()) {
295 bindings_release_capture();
296 vdp_release_framebuffer(coleco->vdp);
297 render_pause_source(coleco->psg->audio);
298 }
299 coleco->should_return = 0;
300 }
301
302 static void resume_coleco(system_header *system)
303 {
304 coleco_context *coleco = (coleco_context *)system;
305 if (coleco->header.force_release || render_should_release_on_exit()) {
306 coleco->header.force_release = 0;
307 bindings_reacquire_capture();
308 vdp_reacquire_framebuffer(coleco->vdp);
309 render_resume_source(coleco->psg->audio);
310 }
311 run_coleco(system);
312 }
313
314 static void start_coleco(system_header *system, char *statefile)
315 {
316 coleco_context *coleco = (coleco_context *)system;
317
318 z80_assert_reset(coleco->z80, 0);
319 z80_clear_reset(coleco->z80, 128*15);
320
321 if (statefile) {
322 load_state_path(coleco, statefile);
323 }
324
325 if (system->enter_debugger) {
326 system->enter_debugger = 0;
327 #ifndef IS_LIB
328 zinsert_breakpoint(coleco->z80, coleco->z80->pc, (uint8_t *)zdebugger);
329 #endif
330 }
331
332 run_coleco(system);
333 }
334
335 static void free_coleco(system_header *system)
336 {
337 coleco_context *coleco = (coleco_context *)system;
338 vdp_free(coleco->vdp);
339 z80_options_free(coleco->z80->Z80_OPTS);
340 free(coleco->z80);
341 psg_free(coleco->psg);
342 free(coleco->rom);
343 free(coleco->header.info.map);
344 free(coleco->header.info.name);
345 free(coleco);
346 }
347
348 static void soft_reset(system_header *system)
349 {
350 coleco_context *coleco = (coleco_context *)system;
351 z80_assert_reset(coleco->z80, coleco->z80->Z80_CYCLE);
352 #ifndef NEW_CORE
353 coleco->z80->target_cycle = coleco->z80->sync_cycle = coleco->z80->Z80_CYCLE;
354 #endif
355 }
356
357
358 static void set_speed_percent(system_header * system, uint32_t percent)
359 {
360 coleco_context *coleco = (coleco_context *)system;
361 uint32_t old_clock = coleco->master_clock;
362 coleco->master_clock = ((uint64_t)coleco->normal_clock * (uint64_t)percent) / 100;
363
364 psg_adjust_master_clock(coleco->psg, coleco->master_clock);
365 }
366
367 static uint16_t get_open_bus_value(system_header *system)
368 {
369 return 0xFFFF;
370 }
371
372 static void request_exit(system_header *system)
373 {
374 coleco_context *coleco = (coleco_context *)system;
375 coleco->should_return = 1;
376 #ifndef NEW_CORE
377 coleco->z80->target_cycle = coleco->z80->sync_cycle = coleco->z80->Z80_CYCLE;
378 #endif
379 }
380
381 static uint8_t button_map[] = {
382 [DPAD_UP] = 1,
383 [DPAD_DOWN] = 4,
384 [DPAD_LEFT] = 8,
385 [DPAD_RIGHT] = 2,
386 [BUTTON_A] = 0x40,
387 [BUTTON_B] = 0x40,
388 [BUTTON_C] = 9, //*
389 [BUTTON_START] = 6, //#
390 [BUTTON_X] = 0xD, //1
391 [BUTTON_Y] = 0x7, //2
392 [BUTTON_Z] = 0xC, //3
393 [BUTTON_MODE] = 0x2 //4
394 };
395
396 static void gamepad_down(system_header *system, uint8_t gamepad_num, uint8_t button)
397 {
398 if (gamepad_num > 2) {
399 return;
400 }
401 coleco_context *coleco = (coleco_context *)system;
402 uint8_t index = gamepad_num - 1;
403 if (button < BUTTON_B) {
404 index += 2;
405 }
406 if (button > BUTTON_B) {
407 coleco->controller_state[index] &= 0xF0;
408 coleco->controller_state[index] |= button_map[button];
409 } else {
410 coleco->controller_state[index] &= ~button_map[button];
411 }
412
413 }
414
415 static void gamepad_up(system_header *system, uint8_t gamepad_num, uint8_t button)
416 {
417 if (gamepad_num > 2) {
418 return;
419 }
420 coleco_context *coleco = (coleco_context *)system;
421 uint8_t index = gamepad_num - 1;
422 if (button < BUTTON_B) {
423 index += 2;
424 }
425 if (button > BUTTON_B) {
426 coleco->controller_state[index] |= 0xF;
427 } else {
428 coleco->controller_state[index] |= button_map[button];
429 }
430 }
431
432
433 static void config_updated(system_header *system)
434 {
435 coleco_context *coleco = (coleco_context *)system;
436 //sample rate may have changed
437 psg_adjust_master_clock(coleco->psg, coleco->master_clock);
438 }
439
440 static void inc_debug_mode(system_header *system)
441 {
442 coleco_context *coleco = (coleco_context *)system;
443 vdp_inc_debug_mode(coleco->vdp);
444 }
445
446 static void toggle_debug_view(system_header *system, uint8_t debug_view)
447 {
448 #ifndef IS_LIB
449 coleco_context *coleco = (coleco_context *)system;
450 if (debug_view < DEBUG_OSCILLOSCOPE) {
451 vdp_toggle_debug_view(coleco->vdp, debug_view);
452 } else if (debug_view == DEBUG_OSCILLOSCOPE) {
453 if (coleco->psg->scope) {
454 oscilloscope *scope = coleco->psg->scope;
455 coleco->psg->scope = NULL;
456 scope_close(scope);
457 } else {
458 oscilloscope *scope = create_oscilloscope();
459 psg_enable_scope(coleco->psg, scope, coleco->normal_clock);
460 }
461 }
462 #endif
463 }
464
465 static void load_save(system_header *system)
466 {
467 //Unclear if any Coleco carts have non-volatile memory
468 //but we need a dummy implementation so the save directory gets setup
469 }
470
471 static void persist_save(system_header *system)
472 {
473 }
474
475 coleco_context *alloc_configure_coleco(system_media *media)
476 {
477 coleco_context *coleco = calloc(1, sizeof(coleco_context));
478 char *bios_path = tern_find_path_default(config, "system\0coleco_bios_path\0", (tern_val){.ptrval = "colecovision_bios.col"}, TVAL_PTR).ptrval;
479 if (is_absolute_path(bios_path)) {
480 FILE *f = fopen(bios_path, "rb");
481 if (f) {
482 fread(coleco->bios, 1, sizeof(coleco->bios), f);
483 fclose(f);
484 } else {
485 warning("Failed to open Colecovision BIOS from %s\n", bios_path);
486 }
487
488 } else {
489 uint32_t bios_size;
490 char *tmp = read_bundled_file(bios_path, &bios_size);
491 if (tmp) {
492 if (bios_size > sizeof(coleco->bios)) {
493 bios_size = sizeof(coleco->bios);
494 }
495 memcpy(coleco->bios, tmp, bios_size);
496 free(tmp);
497 } else {
498 warning("Failed to open Colecovision BIOS from %s\n", bios_path);
499 }
500
501 }
502
503 coleco->rom = media->buffer;
504 coleco->rom_size = media->size;
505 const memmap_chunk map[] = {
506 {0x0000, 0x2000, sizeof(coleco->bios)-1, .flags = MMAP_READ, .buffer = coleco->bios},
507 {0x8000, 0x10000, nearest_pow2(coleco->rom_size)-1, .flags = MMAP_READ, .buffer = coleco->rom},
508 {0x7000, 0x8000, sizeof(coleco->ram)-1, .flags = MMAP_READ|MMAP_WRITE|MMAP_CODE, .buffer = coleco->ram}
509 };
510 static const memmap_chunk io_map[] = {
511 {0x80, 0xA0, 0xFF, .write_8 = coleco_select_write},
512 {0xA0, 0xC0, 0xFF, .read_8 = coleco_vdp_read, .write_8 = coleco_vdp_write},
513 {0xC0, 0xE0, 0xFF, .write_8 = coleco_select_write},
514 {0xE0, 0x100, 0xFF, .read_8 = coleco_controller_read, .write_8 = coleco_psg_write}
515 };
516 uint32_t rom_size = coleco->header.info.rom_size;
517 z80_options *zopts = malloc(sizeof(z80_options));
518 uint32_t num_chunks = sizeof(map)/sizeof(*map);
519 memmap_chunk *heap_map = calloc(num_chunks, sizeof(memmap_chunk));
520 memcpy(heap_map, map, sizeof(map));
521
522 //Colecovision appears to use a 7.15909 MHz crystal with a /2 divider, but /15 works better with my VDP implementation
523 init_z80_opts(zopts, heap_map, num_chunks, io_map, sizeof(io_map)/sizeof(*io_map), 15, 0xFF);
524 coleco->z80 = init_z80_context(zopts);
525 coleco->z80->system = coleco;
526 coleco->normal_clock = coleco->master_clock = 53693175;
527
528 coleco->psg = malloc(sizeof(psg_context));
529 psg_init(coleco->psg, coleco->master_clock, 15*16);
530
531 coleco->vdp = init_vdp_context(0, 0, VDP_TMS9918A);
532 coleco->vdp->system = &coleco->header;
533
534 memset(coleco->controller_state, 0xFF, sizeof(coleco->controller_state));
535
536 coleco->header.info.save_type = SAVE_NONE;
537 coleco->header.info.map = heap_map;
538 //TODO: copy name from header if present
539 coleco->header.info.name = strdup(media->name);
540
541
542 coleco->header.has_keyboard = 0;
543 coleco->header.set_speed_percent = set_speed_percent;
544 coleco->header.start_context = start_coleco;
545 coleco->header.resume_context = resume_coleco;
546 coleco->header.load_save = load_save;
547 coleco->header.persist_save = persist_save;
548 coleco->header.load_state = load_state;
549 coleco->header.free_context = free_coleco;
550 coleco->header.get_open_bus_value = get_open_bus_value;
551 coleco->header.request_exit = request_exit;
552 coleco->header.soft_reset = soft_reset;
553 coleco->header.inc_debug_mode = inc_debug_mode;
554 coleco->header.gamepad_down = gamepad_down;
555 coleco->header.gamepad_up = gamepad_up;
556 //coleco->header.keyboard_down = keyboard_down;
557 //coleco->header.keyboard_up = keyboard_up;
558 coleco->header.config_updated = config_updated;
559 coleco->header.serialize = serialize;
560 coleco->header.deserialize = deserialize;
561 coleco->header.toggle_debug_view = toggle_debug_view;
562 coleco->header.type = SYSTEM_COLECOVISION;
563
564 return coleco;
565 }