Mercurial > repos > blastem
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 } |