-
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathclownmdemu.c
507 lines (408 loc) · 19.3 KB
/
clownmdemu.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
#include "clownmdemu.h"
#include <assert.h>
#include <stddef.h>
#include <string.h>
#include "clowncommon/clowncommon.h"
#include "bus-main-m68k.h"
#include "bus-sub-m68k.h"
#include "bus-z80.h"
#include "clown68000/interpreter/clown68000.h"
#include "fm.h"
#include "log.h"
#include "psg.h"
#include "vdp.h"
#include "z80.h"
#define MAX_ROM_SIZE (1024 * 1024 * 4) /* 4MiB */
/* TODO: Merge this with the functions in 'cdc.c'. */
static cc_u32f ClownMDEmu_U16sToU32(const cc_u16l* const u16s)
{
return (cc_u32f)u16s[0] << 16 | u16s[1];
}
static void CDSectorTo68kRAM(const ClownMDEmu_Callbacks* const callbacks, cc_u16l* const ram)
{
callbacks->cd_sector_read((void*)callbacks->user_data, ram);
}
static void CDSectorsTo68kRAM(const ClownMDEmu_Callbacks* const callbacks, cc_u16l* const ram, const cc_u32f start, const cc_u32f length)
{
cc_u32f i;
callbacks->cd_seeked((void*)callbacks->user_data, start / CDC_SECTOR_SIZE);
for (i = 0; i < CC_DIVIDE_CEILING(length, CDC_SECTOR_SIZE); ++i)
CDSectorTo68kRAM(callbacks, &ram[i * CDC_SECTOR_SIZE / 2]);
}
void ClownMDEmu_Constant_Initialise(ClownMDEmu_Constant* const constant)
{
Z80_Constant_Initialise(&constant->z80);
VDP_Constant_Initialise(&constant->vdp);
FM_Constant_Initialise(&constant->fm);
PSG_Constant_Initialise(&constant->psg);
}
static cc_bool FrontendControllerCallbackCommon(void* const user_data, const Controller_Button button, const cc_u8f joypad_index)
{
ClownMDEmu_Button frontend_button;
const ClownMDEmu_Callbacks *frontend_callbacks = (const ClownMDEmu_Callbacks*)user_data;
switch (button)
{
case CONTROLLER_BUTTON_UP:
frontend_button = CLOWNMDEMU_BUTTON_UP;
break;
case CONTROLLER_BUTTON_DOWN:
frontend_button = CLOWNMDEMU_BUTTON_DOWN;
break;
case CONTROLLER_BUTTON_LEFT:
frontend_button = CLOWNMDEMU_BUTTON_LEFT;
break;
case CONTROLLER_BUTTON_RIGHT:
frontend_button = CLOWNMDEMU_BUTTON_RIGHT;
break;
case CONTROLLER_BUTTON_A:
frontend_button = CLOWNMDEMU_BUTTON_A;
break;
case CONTROLLER_BUTTON_B:
frontend_button = CLOWNMDEMU_BUTTON_B;
break;
case CONTROLLER_BUTTON_C:
frontend_button = CLOWNMDEMU_BUTTON_C;
break;
case CONTROLLER_BUTTON_X:
frontend_button = CLOWNMDEMU_BUTTON_X;
break;
case CONTROLLER_BUTTON_Y:
frontend_button = CLOWNMDEMU_BUTTON_Y;
break;
case CONTROLLER_BUTTON_Z:
frontend_button = CLOWNMDEMU_BUTTON_Z;
break;
case CONTROLLER_BUTTON_START:
frontend_button = CLOWNMDEMU_BUTTON_START;
break;
case CONTROLLER_BUTTON_MODE:
frontend_button = CLOWNMDEMU_BUTTON_MODE;
break;
default:
assert(cc_false);
return cc_false;
}
return frontend_callbacks->input_requested((void*)frontend_callbacks->user_data, joypad_index, frontend_button);
}
static cc_bool FrontendController1Callback(void* const user_data, const Controller_Button button)
{
return FrontendControllerCallbackCommon(user_data, button, 0);
}
static cc_bool FrontendController2Callback(void* const user_data, const Controller_Button button)
{
return FrontendControllerCallbackCommon(user_data, button, 1);
}
static cc_u8f IOPortToController_ReadCallback(void* const user_data, const cc_u16f cycles)
{
const IOPortToController_Parameters *parameters = (const IOPortToController_Parameters*)user_data;
return Controller_Read(parameters->controller, cycles, parameters->frontend_callbacks);
}
static void IOPortToController_WriteCallback(void* const user_data, const cc_u8f value, const cc_u16f cycles)
{
const IOPortToController_Parameters *parameters = (const IOPortToController_Parameters*)user_data;
Controller_Write(parameters->controller, value, cycles);
}
void ClownMDEmu_State_Initialise(ClownMDEmu_State* const state)
{
cc_u16f i;
/* M68K */
/* A real console does not retain its RAM contents between games, as RAM
is cleared when the console is powered-off.
Failing to clear RAM causes issues with Sonic games and ROM-hacks,
which skip initialisation when a certain magic number is found in RAM. */
memset(state->m68k.ram, 0, sizeof(state->m68k.ram));
state->m68k.cycle_countdown = 1;
/* Z80 */
Z80_State_Initialise(&state->z80.state);
memset(state->z80.ram, 0, sizeof(state->z80.ram));
state->z80.cycle_countdown = 1;
state->z80.bank = 0;
state->z80.bus_requested = cc_false; /* This should be false, according to Charles MacDonald's gen-hw.txt. */
state->z80.reset_held = cc_true;
VDP_State_Initialise(&state->vdp);
FM_State_Initialise(&state->fm);
PSG_State_Initialise(&state->psg);
for (i = 0; i < CC_COUNT_OF(state->io_ports); ++i)
{
/* The standard Sega SDK bootcode uses this to detect soft-resets. */
IOPort_Initialise(&state->io_ports[i]);
}
IOPort_SetCallbacks(&state->io_ports[0], IOPortToController_ReadCallback, IOPortToController_WriteCallback);
IOPort_SetCallbacks(&state->io_ports[1], IOPortToController_ReadCallback, IOPortToController_WriteCallback);
Controller_Initialise(&state->controllers[0], FrontendController1Callback);
Controller_Initialise(&state->controllers[1], FrontendController2Callback);
state->external_ram.size = 0;
state->external_ram.non_volatile = cc_false;
state->external_ram.data_size = 0;
state->external_ram.device_type = 0;
state->external_ram.mapped_in = cc_false;
/* Mega CD */
state->mega_cd.m68k.cycle_countdown = 1;
state->mega_cd.m68k.bus_requested = cc_true;
state->mega_cd.m68k.reset_held = cc_true;
state->mega_cd.prg_ram.bank = 0;
state->mega_cd.word_ram.in_1m_mode = cc_false;
/* Page 24 of MEGA-CD HARDWARE MANUAL confirms this. */
state->mega_cd.word_ram.dmna = cc_false;
state->mega_cd.word_ram.ret = cc_true;
state->mega_cd.communication.flag = 0;
for (i = 0; i < CC_COUNT_OF(state->mega_cd.communication.command); ++i)
state->mega_cd.communication.command[i] = 0;
for (i = 0; i < CC_COUNT_OF(state->mega_cd.communication.status); ++i)
state->mega_cd.communication.status[i] = 0;
for (i = 0; i < CC_COUNT_OF(state->mega_cd.irq.enabled); ++i)
state->mega_cd.irq.enabled[i] = cc_false;
state->mega_cd.irq.irq1_pending = cc_false;
state->mega_cd.irq.irq3_countdown_master = state->mega_cd.irq.irq3_countdown = 0;
state->mega_cd.rotation.large_stamp_map = cc_false;
state->mega_cd.rotation.large_stamp = cc_false;
state->mega_cd.rotation.repeating_stamp_map = cc_false;
state->mega_cd.rotation.stamp_map_address = 0;
state->mega_cd.rotation.image_buffer_address = 0;
state->mega_cd.rotation.image_buffer_width = 0;
state->mega_cd.rotation.image_buffer_height = 0;
state->mega_cd.rotation.image_buffer_height_in_tiles = 0;
state->mega_cd.rotation.image_buffer_x_offset = 0;
state->mega_cd.rotation.image_buffer_y_offset = 0;
CDC_Initialise(&state->mega_cd.cdc);
CDDA_Initialise(&state->mega_cd.cdda);
PCM_State_Initialise(&state->mega_cd.pcm);
state->mega_cd.boot_from_cd = cc_false;
state->mega_cd.hblank_address = 0xFFFF;
state->mega_cd.delayed_dma_word = 0;
}
void ClownMDEmu_Parameters_Initialise(ClownMDEmu* const clownmdemu, const ClownMDEmu_Configuration* const configuration, const ClownMDEmu_Constant* const constant, ClownMDEmu_State* const state, const ClownMDEmu_Callbacks* const callbacks)
{
clownmdemu->configuration = configuration;
clownmdemu->constant = constant;
clownmdemu->state = state;
clownmdemu->callbacks = callbacks;
clownmdemu->m68k = &state->m68k.state;
clownmdemu->z80.constant = &constant->z80;
clownmdemu->z80.state = &state->z80.state;
clownmdemu->mcd_m68k = &state->mega_cd.m68k.state;
clownmdemu->vdp.configuration = &configuration->vdp;
clownmdemu->vdp.constant = &constant->vdp;
clownmdemu->vdp.state = &state->vdp;
FM_Parameters_Initialise(&clownmdemu->fm, &configuration->fm, &constant->fm, &state->fm);
clownmdemu->psg.configuration = &configuration->psg;
clownmdemu->psg.constant = &constant->psg;
clownmdemu->psg.state = &state->psg;
clownmdemu->pcm.configuration = &configuration->pcm;
clownmdemu->pcm.state = &state->mega_cd.pcm;
}
void ClownMDEmu_Iterate(const ClownMDEmu* const clownmdemu)
{
const cc_u16f television_vertical_resolution = GetTelevisionVerticalResolution(clownmdemu);
const cc_u16f console_vertical_resolution = (clownmdemu->state->vdp.v30_enabled ? 30 : 28) * 8; /* 240 and 224 */
const CycleMegaDrive cycles_per_frame_mega_drive = GetMegaDriveCyclesPerFrame(clownmdemu);
const cc_u16f cycles_per_scanline = cycles_per_frame_mega_drive.cycle / television_vertical_resolution;
const CycleMegaCD cycles_per_frame_mega_cd = MakeCycleMegaCD(clownmdemu->configuration->general.tv_standard == CLOWNMDEMU_TV_STANDARD_PAL ? CLOWNMDEMU_DIVIDE_BY_PAL_FRAMERATE(CLOWNMDEMU_MCD_MASTER_CLOCK) : CLOWNMDEMU_DIVIDE_BY_NTSC_FRAMERATE(CLOWNMDEMU_MCD_MASTER_CLOCK));
CPUCallbackUserData cpu_callback_user_data;
cc_u8f h_int_counter;
cc_u8f i;
cpu_callback_user_data.clownmdemu = clownmdemu;
cpu_callback_user_data.sync.m68k.current_cycle = 0;
/* TODO: This is awful; stop doing this. */
cpu_callback_user_data.sync.m68k.cycle_countdown = &clownmdemu->state->m68k.cycle_countdown;
cpu_callback_user_data.sync.z80.current_cycle = 0;
cpu_callback_user_data.sync.z80.cycle_countdown = &clownmdemu->state->z80.cycle_countdown;
cpu_callback_user_data.sync.mcd_m68k.current_cycle = 0;
cpu_callback_user_data.sync.mcd_m68k.cycle_countdown = &clownmdemu->state->mega_cd.m68k.cycle_countdown;
cpu_callback_user_data.sync.mcd_m68k_irq3.current_cycle = 0;
cpu_callback_user_data.sync.mcd_m68k_irq3.cycle_countdown = &clownmdemu->state->mega_cd.irq.irq3_countdown;
cpu_callback_user_data.sync.fm.current_cycle = 0;
cpu_callback_user_data.sync.psg.current_cycle = 0;
cpu_callback_user_data.sync.pcm.current_cycle = 0;
for (i = 0; i < CC_COUNT_OF(cpu_callback_user_data.sync.io_ports); ++i)
cpu_callback_user_data.sync.io_ports[i].current_cycle = 0;
/* Reload H-Int counter at the top of the screen, just like real hardware does */
h_int_counter = clownmdemu->state->vdp.h_int_interval;
clownmdemu->state->vdp.currently_in_vblank = cc_false;
for (clownmdemu->state->current_scanline = 0; clownmdemu->state->current_scanline < television_vertical_resolution; ++clownmdemu->state->current_scanline)
{
const cc_u16f scanline = clownmdemu->state->current_scanline;
const CycleMegaDrive current_cycle = MakeCycleMegaDrive(cycles_per_scanline * (1 + scanline));
/* Sync the 68k, since it's the one thing that can influence the VDP */
SyncM68k(clownmdemu, &cpu_callback_user_data, current_cycle);
/* Only render scanlines and generate H-Ints for scanlines that the console outputs to */
if (scanline < console_vertical_resolution)
{
if (clownmdemu->state->vdp.double_resolution_enabled)
{
VDP_RenderScanline(&clownmdemu->vdp, scanline * 2, clownmdemu->callbacks->scanline_rendered, clownmdemu->callbacks->user_data);
VDP_RenderScanline(&clownmdemu->vdp, scanline * 2 + 1, clownmdemu->callbacks->scanline_rendered, clownmdemu->callbacks->user_data);
}
else
{
VDP_RenderScanline(&clownmdemu->vdp, scanline, clownmdemu->callbacks->scanline_rendered, clownmdemu->callbacks->user_data);
}
/* Fire a H-Int if we've reached the requested line */
if (h_int_counter-- == 0)
{
h_int_counter = clownmdemu->state->vdp.h_int_interval;
/* Do H-Int */
if (clownmdemu->state->vdp.h_int_enabled)
Clown68000_Interrupt(clownmdemu->m68k, 4);
}
}
else if (scanline == console_vertical_resolution) /* Check if we have reached the end of the console-output scanlines */
{
/* Do V-Int */
if (clownmdemu->state->vdp.v_int_enabled)
Clown68000_Interrupt(clownmdemu->m68k, 6);
/* According to Charles MacDonald's gen-hw.txt, this occurs regardless of the 'v_int_enabled' setting. */
SyncZ80(clownmdemu, &cpu_callback_user_data, current_cycle);
Z80_Interrupt(&clownmdemu->z80, cc_true);
/* Flag that we have entered the V-blank region */
clownmdemu->state->vdp.currently_in_vblank = cc_true;
}
else if (scanline == console_vertical_resolution + 3) /* TODO: This should be '+1', but a hack is needed until something changes to not make Earthworm Jim 2 a stuttery mess. */
{
/* Assert the Z80 interrupt for a whole scanline. This has the side-effect of causing a second interrupt to occur if the handler exits quickly. */
/* TODO: According to Vladikcomper, this interrupt should be asserted for roughly 171 Z80 cycles. */
SyncZ80(clownmdemu, &cpu_callback_user_data, current_cycle);
Z80_Interrupt(&clownmdemu->z80, cc_false);
}
}
/* Update everything for the rest of the frame. */
SyncM68k(clownmdemu, &cpu_callback_user_data, cycles_per_frame_mega_drive);
SyncZ80(clownmdemu, &cpu_callback_user_data, cycles_per_frame_mega_drive);
SyncMCDM68k(clownmdemu, &cpu_callback_user_data, cycles_per_frame_mega_cd);
SyncFM(&cpu_callback_user_data, cycles_per_frame_mega_drive);
SyncPSG(&cpu_callback_user_data, cycles_per_frame_mega_drive);
SyncPCM(&cpu_callback_user_data, cycles_per_frame_mega_cd);
SyncCDDA(&cpu_callback_user_data, clownmdemu->configuration->general.tv_standard == CLOWNMDEMU_TV_STANDARD_PAL ? CLOWNMDEMU_DIVIDE_BY_PAL_FRAMERATE(44100) : CLOWNMDEMU_DIVIDE_BY_NTSC_FRAMERATE(44100));
/* Fire IRQ1 if needed. */
/* TODO: This is a hack. Look into when this interrupt should actually be done. */
if (clownmdemu->state->mega_cd.irq.irq1_pending)
{
clownmdemu->state->mega_cd.irq.irq1_pending = cc_false;
Clown68000_Interrupt(clownmdemu->mcd_m68k, 1);
}
/* TODO: This should be done 75 times a second (in sync with the CDD interrupt), not 60! */
CDDA_UpdateFade(&clownmdemu->state->mega_cd.cdda);
}
static cc_u8f ReadCartridgeByte(const ClownMDEmu* const clownmdemu, const cc_u32f address)
{
return clownmdemu->callbacks->cartridge_read((void*)clownmdemu->callbacks->user_data, address);
}
static cc_u16f ReadCartridgeWord(const ClownMDEmu* const clownmdemu, const cc_u32f address)
{
cc_u16f word;
word = ReadCartridgeByte(clownmdemu, address + 0) << 8;
word |= ReadCartridgeByte(clownmdemu, address + 1);
return word;
}
static cc_u32f ReadCartridgeLongWord(const ClownMDEmu* const clownmdemu, const cc_u32f address)
{
cc_u32f longword;
longword = ReadCartridgeWord(clownmdemu, address + 0) << 16;
longword |= ReadCartridgeWord(clownmdemu, address + 2);
return longword;
}
static cc_u32f NextPowerOfTwo(cc_u32f v)
{
/* https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 */
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
void ClownMDEmu_Reset(const ClownMDEmu* const clownmdemu, const cc_bool cd_boot)
{
Clown68000_ReadWriteCallbacks m68k_read_write_callbacks;
CPUCallbackUserData callback_user_data;
/* Handle external RAM. */
if (ReadCartridgeWord(clownmdemu, 0x1B0) == ((cc_u16f)'R' << 8 | (cc_u16f)'A' << 0))
{
const cc_u16f metadata = ReadCartridgeWord(clownmdemu, 0x1B2);
const cc_u16f metadata_junk_bits = metadata & 0xA71F;
const cc_u32f start = ReadCartridgeLongWord(clownmdemu, 0x1B4);
const cc_u32f end = ReadCartridgeLongWord(clownmdemu, 0x1B8) + 1;
const cc_u32f size = NextPowerOfTwo(end - 0x200000);
clownmdemu->state->external_ram.size = CC_COUNT_OF(clownmdemu->state->external_ram.buffer);
clownmdemu->state->external_ram.non_volatile = (metadata & 0x4000) != 0;
clownmdemu->state->external_ram.data_size = (metadata >> 11) & 3;
clownmdemu->state->external_ram.device_type = (metadata >> 5) & 7;
if (metadata_junk_bits != 0xA000)
LogMessage("External RAM metadata data at cartridge address 0x1B2 has incorrect junk bits - should be 0xA000, but was 0x%" CC_PRIXFAST16, metadata_junk_bits);
if (clownmdemu->state->external_ram.device_type != 1 && clownmdemu->state->external_ram.device_type != 2)
LogMessage("Invalid external RAM device type - should be 1 or 2, but was %" CC_PRIXLEAST8, clownmdemu->state->external_ram.device_type);
/* TODO: Add support for EEPROM. */
if (clownmdemu->state->external_ram.data_size == 1 || clownmdemu->state->external_ram.device_type == 2)
LogMessage("EEPROM external RAM is not yet supported - use SRAM instead");
/* TODO: Should we just disable SRAM in these events? */
/* TODO: SRAM should probably not be disabled in the first case, since the Sonic 1 disassembly makes this mistake by default. */
if (clownmdemu->state->external_ram.data_size != 3 && start != 0x200000)
{
LogMessage("Invalid external RAM start address - should be 0x200000, but was 0x%" CC_PRIXFAST32, start);
}
else if (clownmdemu->state->external_ram.data_size == 3 && start != 0x200001)
{
LogMessage("Invalid external RAM start address - should be 0x200001, but was 0x%" CC_PRIXFAST32, start);
}
else if (end < start)
{
LogMessage("Invalid external RAM end address - should be after start address but was before it instead");
}
else if (size >= CC_COUNT_OF(clownmdemu->state->external_ram.buffer))
{
LogMessage("External RAM is too large - must be 0x%" CC_PRIXFAST32 " bytes or less, but was 0x%" CC_PRIXFAST32, (cc_u32f)CC_COUNT_OF(clownmdemu->state->external_ram.buffer), size);
}
else
{
clownmdemu->state->external_ram.size = size;
}
}
clownmdemu->state->mega_cd.boot_from_cd = cd_boot;
if (cd_boot)
{
/* Boot from CD ("Mode 2"). */
cc_u32f ip_start, ip_length, sp_start, sp_length;
const cc_u16f boot_header_offset = 0x6000;
const cc_u16f ip_start_default = 0x200;
const cc_u16f ip_length_default = 0x600;
cc_u16l* const sector_words = &clownmdemu->state->mega_cd.prg_ram.buffer[boot_header_offset / 2];
/*cc_u8l region;*/
/* Read first sector. */
clownmdemu->callbacks->cd_seeked((void*)clownmdemu->callbacks->user_data, 0);
clownmdemu->callbacks->cd_sector_read((void*)clownmdemu->callbacks->user_data, sector_words); /* Sega's BIOS reads to PRG-RAM too. */
ip_start = ClownMDEmu_U16sToU32(§or_words[0x18]);
ip_length = ClownMDEmu_U16sToU32(§or_words[0x1A]);
sp_start = ClownMDEmu_U16sToU32(§or_words[0x20]);
sp_length = ClownMDEmu_U16sToU32(§or_words[0x22]);
/*region = sector_bytes[0x1F0];*/
/* Don't allow overflowing the PRG-RAM array. */
sp_length = CC_MIN(CC_COUNT_OF(clownmdemu->state->mega_cd.prg_ram.buffer) * 2 - boot_header_offset, sp_length);
/* Read Initial Program. */
memcpy(clownmdemu->state->mega_cd.word_ram.buffer, §or_words[ip_start_default / 2], ip_length_default);
/* Load additional Initial Program data if necessary. */
if (ip_start != ip_start_default || ip_length != ip_length_default)
CDSectorsTo68kRAM(clownmdemu->callbacks, &clownmdemu->state->mega_cd.word_ram.buffer[ip_length_default / 2], ip_start, 32 * CDC_SECTOR_SIZE);
/* This is what Sega's BIOS does. */
memcpy(clownmdemu->state->m68k.ram, clownmdemu->state->mega_cd.word_ram.buffer, sizeof(clownmdemu->state->m68k.ram) / 2);
/* Read Sub Program. */
CDSectorsTo68kRAM(clownmdemu->callbacks, &clownmdemu->state->mega_cd.prg_ram.buffer[boot_header_offset / 2], sp_start, sp_length);
/* Give WORD-RAM to the SUB-CPU. */
clownmdemu->state->mega_cd.word_ram.dmna = cc_true;
clownmdemu->state->mega_cd.word_ram.ret = cc_false;
}
callback_user_data.clownmdemu = clownmdemu;
m68k_read_write_callbacks.user_data = &callback_user_data;
m68k_read_write_callbacks.read_callback = M68kReadCallback;
m68k_read_write_callbacks.write_callback = M68kWriteCallback;
Clown68000_Reset(clownmdemu->m68k, &m68k_read_write_callbacks);
m68k_read_write_callbacks.read_callback = MCDM68kReadCallback;
m68k_read_write_callbacks.write_callback = MCDM68kWriteCallback;
Clown68000_Reset(clownmdemu->mcd_m68k, &m68k_read_write_callbacks);
}
void ClownMDEmu_SetLogCallback(const ClownMDEmu_LogCallback log_callback, const void* const user_data)
{
SetLogCallback(log_callback, user_data);
Clown68000_SetErrorCallback(log_callback, user_data);
}