diff --git a/makefile.burn_rules b/makefile.burn_rules
index 726941509..98c5fb0dd 100644
--- a/makefile.burn_rules
+++ b/makefile.burn_rules
@@ -160,7 +160,7 @@ depobj = burn.o burn_bitmap.o burn_gun.o burn_led.o burn_shift.o burn_memory.o
\
toa_bcu2.o toa_extratext.o toa_gp9001.o toa_palette.o toaplan1.o toaplan.o \
\
- megadrive.o stm95.o \
+ megadrive.o segacd.o stm95.o \
\
pce.o pce_cd.o \
\
diff --git a/projectfiles/visualstudio-2010/fba_vs2010.vcxproj b/projectfiles/visualstudio-2010/fba_vs2010.vcxproj
index 6bf7b00a8..c106e0987 100644
--- a/projectfiles/visualstudio-2010/fba_vs2010.vcxproj
+++ b/projectfiles/visualstudio-2010/fba_vs2010.vcxproj
@@ -704,6 +704,7 @@ rm %(Filename).exe
+
diff --git a/projectfiles/visualstudio-2010/fba_vs2010.vcxproj.filters b/projectfiles/visualstudio-2010/fba_vs2010.vcxproj.filters
index 46065db82..6d828e8f7 100644
--- a/projectfiles/visualstudio-2010/fba_vs2010.vcxproj.filters
+++ b/projectfiles/visualstudio-2010/fba_vs2010.vcxproj.filters
@@ -3018,6 +3018,9 @@
burn\snd
+
+ Burn\drv\megadrive
+
Burn\drv\megadrive
diff --git a/projectfiles/visualstudio-2015/fbneo_vs2015.vcxproj b/projectfiles/visualstudio-2015/fbneo_vs2015.vcxproj
index 1c9ebc3d8..4a3848c67 100644
--- a/projectfiles/visualstudio-2015/fbneo_vs2015.vcxproj
+++ b/projectfiles/visualstudio-2015/fbneo_vs2015.vcxproj
@@ -655,6 +655,7 @@ del %(Filename).exe
+
diff --git a/projectfiles/visualstudio-2015/fbneo_vs2015.vcxproj.filters b/projectfiles/visualstudio-2015/fbneo_vs2015.vcxproj.filters
index 21e6a76ac..d627630eb 100644
--- a/projectfiles/visualstudio-2015/fbneo_vs2015.vcxproj.filters
+++ b/projectfiles/visualstudio-2015/fbneo_vs2015.vcxproj.filters
@@ -2059,6 +2059,9 @@
burn\snd
+
+ burn\drv\megadrive
+
burn\drv\megadrive
diff --git a/projectfiles/visualstudio-2019/fbneo_vs2019.vcxproj b/projectfiles/visualstudio-2019/fbneo_vs2019.vcxproj
index abda156d3..cb77c0772 100644
--- a/projectfiles/visualstudio-2019/fbneo_vs2019.vcxproj
+++ b/projectfiles/visualstudio-2019/fbneo_vs2019.vcxproj
@@ -655,6 +655,7 @@ del %(Filename).exe
+
diff --git a/projectfiles/visualstudio-2019/fbneo_vs2019.vcxproj.filters b/projectfiles/visualstudio-2019/fbneo_vs2019.vcxproj.filters
index ab3f09bd2..e164fd043 100644
--- a/projectfiles/visualstudio-2019/fbneo_vs2019.vcxproj.filters
+++ b/projectfiles/visualstudio-2019/fbneo_vs2019.vcxproj.filters
@@ -2059,6 +2059,9 @@
burn\snd
+
+ burn\drv\megadrive
+
burn\drv\megadrive
diff --git a/projectfiles/visualstudio-2022/fbneo_vs2022.vcxproj b/projectfiles/visualstudio-2022/fbneo_vs2022.vcxproj
index d85079b20..6722cc351 100644
--- a/projectfiles/visualstudio-2022/fbneo_vs2022.vcxproj
+++ b/projectfiles/visualstudio-2022/fbneo_vs2022.vcxproj
@@ -655,6 +655,7 @@ del %(Filename).exe
+
diff --git a/projectfiles/visualstudio-2022/fbneo_vs2022.vcxproj.filters b/projectfiles/visualstudio-2022/fbneo_vs2022.vcxproj.filters
index a38c4e99a..3e02d5210 100644
--- a/projectfiles/visualstudio-2022/fbneo_vs2022.vcxproj.filters
+++ b/projectfiles/visualstudio-2022/fbneo_vs2022.vcxproj.filters
@@ -2059,6 +2059,9 @@
burn\snd
+
+ burn\drv\megadrive
+
burn\drv\megadrive
diff --git a/projectfiles/visualstudio-2026/fbneo_vs2026.vcxproj b/projectfiles/visualstudio-2026/fbneo_vs2026.vcxproj
index 3c06912b7..2f3664505 100644
--- a/projectfiles/visualstudio-2026/fbneo_vs2026.vcxproj
+++ b/projectfiles/visualstudio-2026/fbneo_vs2026.vcxproj
@@ -348,6 +348,7 @@
+
diff --git a/projectfiles/visualstudio-2026/fbneo_vs2026.vcxproj.filters b/projectfiles/visualstudio-2026/fbneo_vs2026.vcxproj.filters
index 89e491206..1a5ce2f2c 100644
--- a/projectfiles/visualstudio-2026/fbneo_vs2026.vcxproj.filters
+++ b/projectfiles/visualstudio-2026/fbneo_vs2026.vcxproj.filters
@@ -952,6 +952,9 @@
burn\drv\megadrive
+
+ burn\drv\megadrive
+
burn\drv\megadrive
diff --git a/src/burn/burn.h b/src/burn/burn.h
index c53495d37..f243e56a6 100644
--- a/src/burn/burn.h
+++ b/src/burn/burn.h
@@ -746,6 +746,7 @@ int BurnComputeSHA1(const UINT8 *buffer, int buffer_size, char *hash_str);
#define HARDWARE_SEGA_GAME_GEAR (HARDWARE_PREFIX_SEGA_GAME_GEAR)
#define HARDWARE_SEGA_MEGADRIVE (HARDWARE_PREFIX_SEGA_MEGADRIVE)
+#define HARDWARE_SEGA_MEGACD (HARDWARE_PREFIX_SEGA_MEGADRIVE | 0x00010000)
#define HARDWARE_SEGA_SG1000 (HARDWARE_PREFIX_SEGA_SG1000)
#define HARDWARE_SEGA_SG1000_RAMEXP_A (0x1000)
diff --git a/src/burn/drv/megadrive/d_megadrive.cpp b/src/burn/drv/megadrive/d_megadrive.cpp
index e86b2f50d..67b0c06f7 100644
--- a/src/burn/drv/megadrive/d_megadrive.cpp
+++ b/src/burn/drv/megadrive/d_megadrive.cpp
@@ -483,6 +483,47 @@ INT32 MegadriveGetZipName(char** pszName, UINT32 i)
return 0;
}
+// Sega CD / Mega-CD system (bios), the disc image is selected separately
+
+static struct BurnDIPInfo SegaCDDIPList[] = {
+ DIP_OFFSET(0x19)
+ {0x00, 0xff, 0xff, 0x00, NULL },
+ {0x01, 0xff, 0xff, 0x03, NULL },
+
+ {0, 0xfd, 0, 4, "Bios" },
+ {0x00, 0x01, 0x03, 0x00, "Auto (disc region)" },
+ {0x00, 0x01, 0x03, 0x01, "Sega CD (USA, v1.10)" },
+ {0x00, 0x01, 0x03, 0x02, "Mega-CD (Europe, v1.00)" },
+ {0x00, 0x01, 0x03, 0x03, "Mega-CD (Japan, v1.00P)" },
+
+ {0, 0xfe, 0, 4, "32-Col Mode Draw" },
+ {0x01, 0x01, 0x03, 0x00, "No Change" },
+ {0x01, 0x01, 0x03, 0x01, "Center" },
+ {0x01, 0x01, 0x03, 0x02, "Inner Zoom" },
+ {0x01, 0x01, 0x03, 0x03, "Resize Screen/Window" },
+};
+
+STDDIPINFO(SegaCD)
+
+static struct BurnRomInfo md_segacdRomDesc[] = {
+ { "Sega CD BIOS (USA, v1.10).bin", 0x020000, 0xc6d10268, BRF_PRG | BRF_ESS },
+ { "Mega-CD BIOS (Europe, v1.00).bin", 0x020000, 0x529ac15a, BRF_PRG | BRF_ESS | BRF_OPT },
+ { "Mega-CD BIOS (Japan, v1.00P).bin", 0x020000, 0x9d2da8f2, BRF_PRG | BRF_ESS | BRF_OPT },
+};
+
+STD_ROM_PICK(md_segacd)
+STD_ROM_FN(md_segacd)
+
+struct BurnDriver BurnDrvmd_segacd = {
+ "md_segacd", NULL, NULL, NULL, "1992",
+ "Sega CD / Mega-CD System\0", "System - media selected separately", "Sega", "Sega CD / Mega-CD",
+ NULL, NULL, NULL, NULL,
+ BDF_GAME_WORKING | BDF_16BIT_ONLY, 2, HARDWARE_SEGA_MEGACD, GBF_BIOS, 0,
+ MegadriveGetZipName, md_segacdRomInfo, md_segacdRomName, NULL, NULL, NULL, NULL, MegadriveInputInfo, SegaCDDIPInfo,
+ MegadriveInitSegaCD, MegadriveExit, MegadriveFrame, MegadriveDraw, MegadriveScan,
+ &bMegadriveRecalcPalette, 0x100, 320, 224, 4, 3
+};
+
// 16 Ton (Japan, SegaNet)
static struct BurnRomInfo md_16tonRomDesc[] = {
{ "16 Ton (Japan, SegaNet)(1991)(Sega).bin", 0x040000, 0x537f04b6, BRF_PRG | SEGA_MD_ROM_LOAD16_WORD_SWAP | SEGA_MD_ROM_OFFS_000000 },
diff --git a/src/burn/drv/megadrive/megadrive.cpp b/src/burn/drv/megadrive/megadrive.cpp
index 1d2368118..1cbd4f19b 100644
--- a/src/burn/drv/megadrive/megadrive.cpp
+++ b/src/burn/drv/megadrive/megadrive.cpp
@@ -33,6 +33,7 @@
#include "m68000_debug.h"
#include "i2ceeprom.h" // i2c eeprom for MD
#include "burn_gun.h" // menacer, justifier
+#include "segacd.h"
UINT8 MegadriveUnmappedRom = 0xff;
@@ -67,7 +68,7 @@ static INT32 SekCyclesDoneFrameF() { return ( (SekCycleCnt - SekCycleCntDELTA) -
#define SekCyclesBurn(c) { SekCycleCnt += c; }
#define SekEndRun(after) { SekCycleCnt -= m68k_ICount - (after); m68k_ICount = after; }
-static void SekRunM68k(INT32 cyc)
+static void SekRunM68kSlice(INT32 cyc)
{
INT32 cyc_do;
@@ -81,6 +82,24 @@ static void SekRunM68k(INT32 cyc)
m68k_ICount = 0;
}
+// Sega CD: the sub cpu catches up with the main cpu in small slices
+#define SEGACD_SLICE 122
+
+static void SekRunM68k(INT32 cyc)
+{
+ if (SegaCDMode) {
+ while (cyc > 0) {
+ INT32 slice = (cyc > SEGACD_SLICE) ? SEGACD_SLICE : cyc;
+ SekRunM68kSlice(slice);
+ SegaCDRunSub(SekCyclesDoneFrame());
+ cyc -= slice;
+ }
+ return;
+ }
+
+ SekRunM68kSlice(cyc);
+}
+
static UINT64 z80_cycle_cnt;
static INT32 Z80HasBus = 0;
static INT32 MegadriveZ80Reset = 0;
@@ -275,6 +294,12 @@ static INT32 dma_xfers = 0; // vdp dma
static INT32 BlankedLine = 0;
static INT32 interlacemode2 = 0;
+// VSRAM writes done once the next line's display has started, kept out of that line (Puggsy FMV)
+static INT32 vsram_late_count = 0;
+static UINT8 vsram_late_idx[0x40];
+static UINT16 vsram_late_val[0x40];
+static void VSRAMLateWrite(INT32 idx);
+
static UINT8 Hardware;
static UINT8 bNoDebug = 0;
static INT32 bForce3Button = 0;
@@ -293,6 +318,12 @@ static UINT8 __fastcall MegadriveReadByte(UINT32 address);
static void MegadriveCheckHardware()
{
+ if (SegaCDMode) {
+ Hardware = SegaCDGetHardware();
+ bprintf(PRINT_IMPORTANT, _T("Emulating %s Mega CD / Sega CD\n"), ((Hardware & 0xc0) == 0xc0) ? _T("Europe PAL") : ((Hardware & 0x80) ? _T("USA NTSC") : _T("Japan NTSC")));
+ return;
+ }
+
Hardware = MegadriveDIP[0] & 0xe0;
if (MegadriveDIP[0] & 0x01) {
// Auto Detect Region and SECAM
@@ -752,6 +783,11 @@ static void DmaSlow(INT32 len)
if ((source & 0xe00000) == 0xe00000) { // RAM
pd = (UINT16 *)(Ram68K + (source & 0xfffe));
pdend = (UINT16 *)(Ram68K + 0x10000);
+ } else if (SegaCDMode && source < 0x400000) { // Sega CD: bios, prg-ram window, word ram
+ fromrom = 1;
+ source &= ~1;
+ // dma from word ram responds with a one-access delay
+ if ((source & 0x3c0000) == 0x200000) source -= 2;
} else if( source < RomSize) { // ROM
if (papriummode || psolarmode) fromrom = 1;
fromrom = 1;
@@ -762,7 +798,7 @@ static void DmaSlow(INT32 len)
} else return; // Invalid source address
// overflow protection, might break something..
- if (len > pdend - pd) {
+ if (pd && len > pdend - pd) {
len = pdend - pd;
//bprintf(0, _T("DmaSlow() overflow(!).\n"));
}
@@ -852,7 +888,12 @@ static void DmaSlow(INT32 len)
a |= RamVReg->addr_u << 16;
for(; len; len--)
{
- VideoWrite128(a, BURN_ENDIAN_SWAP_INT16(*pd++));
+ if (pd == NULL) {
+ VideoWrite128(a, SekReadWord(source));
+ source += 2;
+ } else {
+ VideoWrite128(a, BURN_ENDIAN_SWAP_INT16(*pd++));
+ }
// AutoIncrement
a = (a + inc) & 0x1ffff;
}
@@ -860,6 +901,7 @@ static void DmaSlow(INT32 len)
break;
}
+
// remember addr
RamVReg->addr = (UINT16)a;
UpdateDMARegs();
@@ -1057,6 +1099,32 @@ struct h_ { UINT16 hres; UINT8 hint; UINT8 start; UINT8 end; };
static const h_ hints[2] = { {256, 0x85, 0x94, 0xe9}, {320, 0xa5, 0xb7, 0xe5} };
+static void VSRAMLateWrite(INT32 idx)
+{
+ INT32 cyc = SekCyclesLine();
+ if (cyc < 400 || cyc >= 512) return;
+
+ const UINT8 *hc = (RamVReg->reg[12] & 1) ? hcounts_40 : hcounts_32;
+ if (hc[cyc] >= 0x80) return; // display of the next line not started yet
+
+ for (INT32 i = 0; i < vsram_late_count; i++)
+ if (vsram_late_idx[i] == idx) return;
+
+ vsram_late_idx[vsram_late_count] = idx;
+ vsram_late_val[vsram_late_count] = RamSVid[idx];
+ vsram_late_count++;
+}
+
+// exchanges the late values with the ones they replaced
+static void VSRAMLateSwap()
+{
+ for (INT32 i = 0; i < vsram_late_count; i++) {
+ UINT16 t = RamSVid[vsram_late_idx[i]];
+ RamSVid[vsram_late_idx[i]] = vsram_late_val[i];
+ vsram_late_val[i] = t;
+ }
+}
+
static UINT16 __fastcall MegadriveVideoReadWord(UINT32 sekAddress)
{
if (sekAddress > 0xC0001F)
@@ -1179,6 +1247,8 @@ static void __fastcall MegadriveVideoWriteWord(UINT32 sekAddress, UINT16 wordVal
CalcCol((RamVReg->addr >> 1) & 0x003f, wordValue);
break;
case 5:
+ // limited to Sega CD for safety, but should be valid for regular MD games too
+ if (SegaCDMode) VSRAMLateWrite((RamVReg->addr >> 1) & 0x003f);
RamSVid[(RamVReg->addr >> 1) & 0x003f] = BURN_ENDIAN_SWAP_INT16(wordValue & 0x7ff);
break;
case 0x81: {
@@ -1603,6 +1673,10 @@ static INT32 MegadriveResetDo()
m68k_megadrive_sr_checkint_mode(1);
SekClose();
+ if (SegaCDMode) {
+ SegaCDReset();
+ }
+
ZetOpen(0);
ZetReset();
ZetClose();
@@ -1622,6 +1696,10 @@ static INT32 MegadriveResetDo()
MegadriveCheckHardware();
+ if (SegaCDMode) {
+ SegaCDSetPAL(Hardware & 0x40);
+ }
+
if (last_hardware != Hardware) {
bprintf(0, _T("** Megadrive Region/Hardware change, %dhz\n"), (Hardware & 0x40) ? 50 : 60);
if (Hardware & 0x40) {
@@ -1707,6 +1785,7 @@ static INT32 MegadriveResetDo()
RamVReg->rendstatus = 0;
bMegadriveRecalcPalette = 1;
interlacemode2 = 0;
+ vsram_late_count = 0;
SekCyclesReset();
z80CyclesReset();
@@ -1731,7 +1810,13 @@ static INT32 __fastcall MegadriveIrqCallback(INT32 irq)
case 4: RamVReg->pending_ints = 0x00; break;
case 6: RamVReg->pending_ints &= ~0x20; break;
}
- SekSetIRQLine(0, CPU_IRQSTATUS_NONE);
+ // a pending h-int stays asserted once the v-int is acknowledged (SoulStar demo)
+ // limited to Sega CD for safety, but should be valid for regular MD games too
+ if (SegaCDMode && irq == 6 && (RamVReg->pending_ints & 0x10) && (RamVReg->reg[0] & 0x10)) {
+ SekSetIRQLine(4, CPU_IRQSTATUS_ACK);
+ } else {
+ SekSetIRQLine(0, CPU_IRQSTATUS_NONE);
+ }
return M68K_INT_ACK_AUTOVECTOR;
}
@@ -3825,6 +3910,14 @@ INT32 MegadriveInitColocodx()
return rc;
}
+INT32 MegadriveInitSegaCD()
+{
+ SegaCDMode = 1;
+ SegaCDMainCycles = SekCyclesDoneFrameF;
+
+ return MegadriveInit();
+}
+
INT32 MegadriveInit()
{
BurnAllocMemIndex();
@@ -3835,8 +3928,14 @@ INT32 MegadriveInit()
memset(RomMain, MegadriveUnmappedRom, MAX_CARTRIDGE_SIZE);
- MegadriveLoadRoms(0);
- if (MegadriveLoadRoms(1)) return 1;
+ if (SegaCDMode) {
+ if (SegaCDLoadBios(RomMain)) return 1;
+ RomNum = 1;
+ RomSize = 0x20000;
+ } else {
+ MegadriveLoadRoms(0);
+ if (MegadriveLoadRoms(1)) return 1;
+ }
{
SekInit(0, 0x68000); // Allocate 68000
@@ -3905,8 +4004,12 @@ INT32 MegadriveInit()
SN76496SetBuffered(SekCyclesDoneFrameF, OSC_NTSC / 7);
SN76496SetRoute(0, 0.50, BURN_SND_ROUTE_BOTH);
- MegadriveSetupSRAM();
- SetupCustomCartridgeMappers();
+ if (SegaCDMode) {
+ if (SegaCDInit(RomMain)) return 1;
+ } else {
+ MegadriveSetupSRAM();
+ SetupCustomCartridgeMappers();
+ }
if (MegadriveCallback) MegadriveCallback();
@@ -3960,6 +4063,10 @@ INT32 MegadriveExit()
sot4wmode = 0;
}
+ if (SegaCDMode) {
+ SegaCDExit();
+ }
+
if (has_gun != 0) {
BurnGunExit();
@@ -5520,6 +5627,10 @@ INT32 MegadriveFrame()
SekCyclesNewFrame(); // for sound sync
ZetNewFrame();
+ if (SegaCDMode) {
+ SegaCDNewFrame();
+ }
+
SekOpen(0);
ZetOpen(0);
@@ -5666,6 +5777,8 @@ INT32 MegadriveFrame()
}
// decide if we draw this line
+ INT32 vsram_late = vsram_late_count;
+ if (vsram_late) VSRAMLateSwap();
if ((!(RamVReg->reg[1]&8) && y<=224) || ((RamVReg->reg[1]&8) && y<240)) {
if (interlacemode2) {
RamVReg->field = 0;
@@ -5678,6 +5791,10 @@ INT32 MegadriveFrame()
PicoLine(y);
}
}
+ if (vsram_late) {
+ VSRAMLateSwap();
+ vsram_late_count = 0;
+ }
z80CyclesSync();
@@ -5710,6 +5827,10 @@ INT32 MegadriveFrame()
#endif
}
+ if (SegaCDMode) {
+ SegaCDEndFrame(SekCyclesDoneFrame());
+ }
+
if (pBurnDraw) MegadriveDraw();
if (pBurnSoundOut) {
@@ -5727,6 +5848,10 @@ INT32 MegadriveFrame()
vx_render(pBurnSoundOut, nBurnSoundLen);
}
+ if (SegaCDMode) {
+ SegaCDSoundUpdate(pBurnSoundOut, nBurnSoundLen);
+ }
+
SekClose();
ZetClose();
@@ -5801,6 +5926,10 @@ INT32 MegadriveScan(INT32 nAction, INT32 *pnMin)
md_eeprom_stm95_scan(nAction);
}
+ if (SegaCDMode) {
+ SegaCDScan(nAction, pnMin);
+ }
+
if (nAction & ACB_WRITE && (BurnDrvGetHardwareCode() & 0xff) == HARDWARE_SEGA_MEGADRIVE_PCB_SSF2) {
for (INT32 i = 0; i < 7; i++) {
Ssf2BankWriteByte(0xa130f3 + (i*2), RamMisc->MapperBank[i+1]);
diff --git a/src/burn/drv/megadrive/megadrive.h b/src/burn/drv/megadrive/megadrive.h
index fb998923b..c1e325973 100644
--- a/src/burn/drv/megadrive/megadrive.h
+++ b/src/burn/drv/megadrive/megadrive.h
@@ -36,6 +36,7 @@ INT32 MegadriveInitPaprium();
INT32 MegadriveInitPsolar();
INT32 MegadriveInitSot4w();
INT32 MegadriveInitColocodx();
+INT32 MegadriveInitSegaCD();
INT32 MegadriveExit();
INT32 MegadriveFrame();
diff --git a/src/burn/drv/megadrive/meson.build b/src/burn/drv/megadrive/meson.build
index 50f8967d4..15ddd01c7 100644
--- a/src/burn/drv/megadrive/meson.build
+++ b/src/burn/drv/megadrive/meson.build
@@ -7,6 +7,7 @@
group_desc = 'Sega Mega Drive drivers'
group_srcs = [
'megadrive.cpp',
+ 'segacd.cpp',
'stm95.cpp',
]
drivers = {
diff --git a/src/burn/drv/megadrive/segacd.cpp b/src/burn/drv/megadrive/segacd.cpp
new file mode 100644
index 000000000..cf332e80a
--- /dev/null
+++ b/src/burn/drv/megadrive/segacd.cpp
@@ -0,0 +1,2295 @@
+// FB Neo Sega CD / Mega CD add-on
+// Ported from the ares Mega CD core (ISC license)
+
+#include "burnint.h"
+#include "m68000_intf.h"
+#include "megadrive.h"
+#include "segacd.h"
+#include "cd_interface.h"
+#include "stream.h"
+#include
+
+INT32 SegaCDMode = 0;
+INT32 (*SegaCDMainCycles)() = NULL;
+
+#define SCD_CLOCK 12500000
+#define SCD_PCM_RATE (SCD_CLOCK / 384)
+#define MAIN_NTSC (53693175 / 7)
+#define MAIN_PAL (53203424 / 7)
+
+static UINT8 *AllMemScd = NULL;
+static UINT8 *PrgRam; // 512KiB, Sek byte order (directly mapped on the sub cpu)
+static UINT16 *WordRam; // 256KiB
+static UINT16 *CdcRam; // 16KiB
+static UINT8 *PcmRam; // 64KiB
+static UINT8 *BackupRam; // 8KiB
+static UINT8 *BiosRom;
+
+static INT32 scd_bios_index = 0;
+static INT32 main_clock = MAIN_NTSC;
+static INT32 sub_cycles_frame = 0;
+static INT32 sub_carry = 0;
+
+// gate array
+static UINT8 sub_run;
+static UINT8 sub_request;
+static UINT8 sub_halt;
+static UINT8 sub_reset_pending;
+static UINT8 sub_wram_wait; // 1: blocked inside an instruction, 2: waiting for word ram
+static UINT8 wram_mode; // 0 = 2Mbit, 1 = 1Mbit
+static UINT8 wram_switch_request;
+static UINT8 wram_switch;
+static UINT8 wram_select;
+static UINT8 wram_priority;
+static UINT8 pram_bank;
+static UINT8 pram_protect;
+static UINT32 vector_level4;
+static UINT8 led_status;
+static UINT8 comm_cfm;
+static UINT8 comm_cfs;
+static UINT16 comm_command[8];
+static UINT16 comm_status[8];
+static UINT32 counter_divider;
+static UINT32 counter_dma;
+
+// sub cpu interrupts: bit n = level n
+static UINT8 irq_mask;
+static UINT8 irq_pending;
+static UINT8 irq_dirty;
+
+static UINT8 timer_frequency;
+static UINT8 timer_counter;
+
+// Sanyo LC8951 CD controller
+struct scd_cdc {
+ UINT8 address;
+ UINT16 stopwatch;
+
+ UINT8 irq_decoder_enable, irq_decoder_pending;
+ UINT8 irq_transfer_enable, irq_transfer_pending;
+ UINT8 irq_command_enable, irq_command_pending;
+
+ UINT8 cmd_fifo[8], cmd_read, cmd_write, cmd_empty;
+ UINT8 st_fifo[8], st_read, st_write, st_empty, st_enable, st_active, st_busy, st_wait;
+
+ UINT8 tr_destination;
+ UINT32 tr_address;
+ UINT16 tr_source, tr_target, tr_pointer, tr_length;
+ UINT8 tr_enable, tr_active, tr_busy, tr_wait, tr_ready, tr_completed;
+
+ UINT8 dec_enable, dec_mode, dec_form, dec_valid;
+
+ UINT8 head_minute, head_second, head_frame, head_mode;
+ UINT8 sub_file, sub_channel, sub_submode, sub_coding;
+
+ UINT8 ctrl_head, ctrl_mode, ctrl_form, ctrl_command_break, ctrl_mode_byte_check;
+ UINT8 ctrl_erasure_request, ctrl_write_request, ctrl_pcode, ctrl_qcode, ctrl_auto;
+ UINT8 ctrl_error, ctrl_edc, ctrl_correction_write, ctrl_descramble, ctrl_sync;
+ UINT8 ctrl_sync_irq, ctrl_erasure_correction, ctrl_status_trigger, ctrl_status_control;
+};
+
+static scd_cdc cdc;
+
+// NEC uPD75006 CD drive controller (HLE)
+enum {
+ CDD_STOPPED = 0x0, CDD_PLAYING = 0x1, CDD_SEEKING = 0x2, CDD_SCANNING = 0x3,
+ CDD_PAUSED = 0x4, CDD_DOOR_OPENED = 0x5, CDD_CHECKSUM_ERROR = 0x6, CDD_COMMAND_ERROR = 0x7,
+ CDD_FUNCTION_ERROR = 0x8, CDD_READING_TOC = 0x9, CDD_TRACKING = 0xa, CDD_NO_DISC = 0xb,
+ CDD_LEAD_OUT = 0xc, CDD_LEAD_IN = 0xd, CDD_TRAY_MOVING = 0xe, CDD_TEST = 0xf
+};
+
+struct scd_cdd {
+ UINT16 counter;
+ UINT8 host_clock_enable;
+ UINT8 status_pending;
+ UINT8 status[10];
+ UINT8 command[10];
+
+ UINT8 io_status;
+ UINT8 io_seeking;
+ UINT16 io_latency;
+ INT32 io_sector;
+ INT32 io_track;
+ UINT8 io_toc_read;
+
+ UINT8 dac_rate;
+ UINT8 dac_deemphasis;
+ UINT16 dac_attenuator;
+ UINT16 dac_attenuated;
+
+ UINT8 audio_playing;
+ INT32 audio_track;
+};
+
+static scd_cdd cdd;
+
+// table of contents, rebuilt from the cd image (not saved in states)
+static INT32 disc_present = 0;
+static INT32 toc_first = 0, toc_last = 0, toc_leadout = 0;
+static INT32 toc_lba[100]; // index 1
+static INT32 toc_start[100]; // index 0, the pregap belongs to the track
+static UINT8 toc_isdata[100];
+
+// graphics asic (stamp rotation / scaling)
+struct scd_gpu {
+ UINT8 font_bg, font_fg;
+ UINT16 font_data;
+
+ UINT8 stamp_repeat, stamp_tile_size, stamp_map_size;
+ UINT32 stamp_map_base, stamp_map_address;
+
+ UINT32 image_base;
+ UINT8 image_offset, image_vcells, image_vdots;
+ UINT16 image_hdots;
+ UINT32 image_address;
+
+ UINT32 vector_base, vector_address;
+
+ UINT8 active;
+ UINT32 counter, period;
+};
+
+static scd_gpu gpu;
+
+// Ricoh RF5C164
+struct scd_pcm_channel {
+ UINT8 enable, envelope, pan, start;
+ UINT16 step, loop;
+ UINT32 address;
+};
+
+static scd_pcm_channel pcm_ch[8];
+static UINT8 pcm_enable, pcm_bank, pcm_channel;
+
+#define PCM_FIFO_SIZE 4096
+static INT16 pcm_fifo[PCM_FIFO_SIZE][2];
+static INT32 pcm_fifo_read, pcm_fifo_count;
+static INT16 pcm_last[2];
+static Stream pcm_stream;
+
+static inline UINT8 dec2bcd(INT32 v) { return ((v / 10) << 4) | (v % 10); }
+static inline INT32 bcd2dec(UINT8 v) { return ((v >> 4) * 10) + (v & 0x0f); }
+
+// -------------------------------------------------------------------------
+// sub cpu interrupts
+
+static void sub_irq_update()
+{
+ INT32 level = 0;
+ for (INT32 i = 6; i >= 1; i--) {
+ if (irq_pending & (1 << i)) { level = i; break; }
+ }
+
+ if (level) {
+ SekSetIRQLine(level, CPU_IRQSTATUS_ACK);
+ } else {
+ SekSetIRQLine(0, CPU_IRQSTATUS_NONE);
+ }
+
+ irq_dirty = 0;
+}
+
+static void irq_sync()
+{
+ // the sub cpu line can only be changed while it is the active cpu
+ if (SekGetActive() == 1) {
+ sub_irq_update();
+ } else {
+ irq_dirty = 1;
+ }
+}
+
+static void irq_raise(INT32 level)
+{
+ UINT8 bit = 1 << level;
+ if (irq_pending & bit) return;
+ if (irq_mask & bit) {
+ irq_pending |= bit;
+ irq_sync();
+ }
+}
+
+static void irq_lower(INT32 level)
+{
+ UINT8 bit = 1 << level;
+ if (~irq_pending & bit) return;
+ irq_pending &= ~bit;
+ irq_sync();
+}
+
+static INT32 __fastcall sub_irq_callback(INT32 irq)
+{
+ if (irq >= 1 && irq <= 6) irq_pending &= ~(1 << irq);
+ sub_irq_update();
+
+ return M68K_INT_ACK_AUTOVECTOR;
+}
+
+// -------------------------------------------------------------------------
+// memory helpers
+
+static inline UINT16 prg_read_word(UINT32 a)
+{
+ return BURN_ENDIAN_SWAP_INT16(*((UINT16*)(PrgRam + (a & 0x7fffe))));
+}
+
+static inline void prg_write(UINT32 a, UINT16 d, INT32 upper, INT32 lower)
+{
+ a &= 0x7fffe;
+ if (upper) PrgRam[(a | 0) ^ 1] = d >> 8;
+ if (lower) PrgRam[(a | 1) ^ 1] = d & 0xff;
+}
+
+static inline void wram_write(UINT32 idx, UINT16 d, INT32 upper, INT32 lower)
+{
+ UINT16 *p = &WordRam[idx & 0x1ffff];
+ if (upper && lower) {
+ *p = d;
+ } else if (upper) {
+ *p = (*p & 0x00ff) | (d & 0xff00);
+ } else if (lower) {
+ *p = (*p & 0xff00) | (d & 0x00ff);
+ }
+}
+
+// 1Mbit mode, main cpu side: 0x220000-0x23ffff are cell-mapped views of the bank
+static UINT32 wram_main_1m_index(UINT32 a)
+{
+ if (a >= 0x220000) {
+ if (a < 0x230000) {
+ a = ((a & 0x0fc00) >> 8) | ((a & 0x003fc) << 6) | (a & 0x10002);
+ } else if (a < 0x238000) {
+ a = ((a & 0x07e00) >> 7) | ((a & 0x001fc) << 6) | (a & 0x18002);
+ } else if (a < 0x23c000) {
+ a = ((a & 0x03f00) >> 6) | ((a & 0x000fc) << 6) | (a & 0x1c002);
+ } else {
+ a = ((a & 0x01f80) >> 5) | ((a & 0x0007c) << 6) | (a & 0x1e002);
+ }
+ }
+
+ return (a & 0x1fffe) | wram_select;
+}
+
+// -------------------------------------------------------------------------
+// table of contents
+
+static void toc_build()
+{
+ disc_present = 0;
+ toc_first = toc_last = toc_leadout = 0;
+ memset(toc_lba, 0, sizeof(toc_lba));
+ memset(toc_start, 0, sizeof(toc_start));
+ memset(toc_isdata, 0, sizeof(toc_isdata));
+
+ UINT8 *toc = CDEmuReadTOC(CDEmuTOC_FIRSTLAST);
+ if (toc == NULL) return;
+
+ INT32 first = bcd2dec(toc[0]);
+ INT32 last = bcd2dec(toc[1]);
+ if (first < 1 || last < first || last > 99) return;
+
+ for (INT32 t = first; t <= last; t++) {
+ toc = CDEmuReadTOC(dec2bcd(t));
+ if (toc == NULL) return;
+ toc_lba[t] = (bcd2dec(toc[0]) * 60 + bcd2dec(toc[1])) * 75 + bcd2dec(toc[2]) - 150;
+ toc_isdata[t] = (toc[3] & 0x04) ? 1 : 0;
+ toc_start[t] = (t == first) ? -150 : toc_lba[t];
+ }
+
+ for (INT32 t = first; t < last; t++) {
+ toc = CDEmuReadTOC(CDEmuTOC_TRACKEND | dec2bcd(t));
+ if (toc == NULL) break;
+ INT32 end = (bcd2dec(toc[0]) * 60 + bcd2dec(toc[1])) * 75 + bcd2dec(toc[2]) - 150;
+ if (end > toc_lba[t] && end <= toc_lba[t + 1]) toc_start[t + 1] = end;
+ }
+
+ toc = CDEmuReadTOC(CDEmuTOC_LASTMSF);
+ if (toc == NULL) return;
+ toc_leadout = (bcd2dec(toc[0]) * 60 + bcd2dec(toc[1])) * 75 + bcd2dec(toc[2]) - 150;
+
+ toc_first = first;
+ toc_last = last;
+ disc_present = 1;
+
+ bprintf(0, _T("Sega CD: %d track(s), lead-out at lba %d\n"), last - first + 1, toc_leadout);
+}
+
+static INT32 cdd_in_track(INT32 lba)
+{
+ if (!disc_present || lba < -150 || lba >= toc_leadout) return 0;
+
+ for (INT32 t = toc_last; t > toc_first; t--) {
+ if (lba >= toc_start[t]) return t;
+ }
+
+ return toc_first;
+}
+
+static inline INT32 cdd_in_lead_in(INT32 lba) { return disc_present && lba < -150; }
+static inline INT32 cdd_in_lead_out(INT32 lba) { return disc_present && lba >= toc_leadout; }
+static inline INT32 track_valid(INT32 t) { return disc_present && t >= toc_first && t <= toc_last; }
+static inline INT32 track_is_data(INT32 t) { return track_valid(t) && toc_isdata[t]; }
+static inline INT32 track_is_audio(INT32 t) { return track_valid(t) && !toc_isdata[t]; }
+
+static void msf_from_aba(INT32 frames, UINT8 *m, UINT8 *s, UINT8 *f)
+{
+ if (frames < 0) frames += 100 * 60 * 75;
+ if (frames < 0 || frames >= 100 * 60 * 75) {
+ *m = *s = *f = 0;
+ return;
+ }
+
+ *m = frames / (75 * 60) % 100;
+ *s = frames / 75 % 60;
+ *f = frames % 75;
+}
+
+static void msf_from_lba(INT32 lba, UINT8 *m, UINT8 *s, UINT8 *f)
+{
+ msf_from_aba(lba + 150, m, s, f);
+}
+
+static INT32 msf_to_lba(INT32 m, INT32 s, INT32 f)
+{
+ if (m > 99 || s > 59 || f > 74) return 100 * 60 * 75;
+
+ INT32 frames = m * 60 * 75 + s * 75 + f;
+ if (m >= 90) frames -= 100 * 60 * 75;
+
+ return frames - 150;
+}
+
+// -------------------------------------------------------------------------
+// CDC
+
+static void cdc_poll()
+{
+ INT32 pending = 0;
+ pending |= cdc.irq_decoder_enable && cdc.irq_decoder_pending;
+ pending |= cdc.irq_transfer_enable && cdc.irq_transfer_pending;
+ pending |= cdc.irq_command_enable && cdc.irq_command_pending;
+
+ if (pending) {
+ irq_raise(5);
+ } else {
+ irq_lower(5);
+ }
+}
+
+static void cdc_transfer_complete()
+{
+ cdc.tr_active = 0;
+ cdc.tr_busy = 0;
+ cdc.tr_ready = 0;
+ cdc.tr_completed = 1;
+ cdc.irq_transfer_pending = 1;
+ cdc_poll();
+}
+
+static void cdc_transfer_stop()
+{
+ cdc.tr_active = 0;
+ cdc.tr_busy = 0;
+ cdc.tr_ready = 0;
+}
+
+static void cdc_transfer_start()
+{
+ if (!cdc.tr_enable) return;
+
+ cdc.tr_active = 1;
+ cdc.tr_busy = 1;
+ cdc.tr_ready = (cdc.tr_destination == 2 || cdc.tr_destination == 3);
+ cdc.tr_completed = 0;
+ cdc.irq_transfer_pending = 0;
+ cdc_poll();
+}
+
+static inline void cdc_length_step()
+{
+ if (cdc.tr_length == 0) {
+ cdc_transfer_complete();
+ cdc.tr_length = 0xfff;
+ } else {
+ cdc.tr_length--;
+ }
+}
+
+static UINT16 cdc_transfer_read()
+{
+ if (!cdc.tr_ready) return 0xffff;
+
+ UINT16 data = CdcRam[(cdc.tr_source >> 1) & 0x1fff];
+ cdc.tr_source += 2;
+ cdc_length_step();
+ cdc_length_step();
+
+ return data;
+}
+
+static void pcm_write(UINT32 index, UINT8 data); // forward
+static void sub_write16(UINT32 a, UINT16 d, INT32 upper, INT32 lower); // forward
+
+static void cdc_transfer_dma()
+{
+ if (!cdc.tr_active) return;
+ UINT8 dest = cdc.tr_destination;
+ if (dest != 4 && dest != 5 && dest != 7) return;
+
+ UINT16 data = CdcRam[(cdc.tr_source >> 1) & 0x1fff];
+
+ switch (dest) {
+ case 4: { // pcm wave ram, two byte writes per transfer
+ UINT32 offs = 0x1000 | ((cdc.tr_address >> 1) & 0xfff);
+ pcm_write(offs | 1, data >> 8);
+ pcm_write(offs | 0, data & 0xff);
+ cdc.tr_address += 2;
+ }
+ break;
+
+ case 5:
+ sub_write16(cdc.tr_address & 0x7fffe, data, 1, 1);
+ break;
+
+ case 7:
+ if (wram_mode == 0) {
+ WordRam[(cdc.tr_address >> 1) & 0x1ffff] = data;
+ } else {
+ sub_write16(0x0c0000 | (cdc.tr_address & 0x1fffe), data, 1, 1);
+ }
+ break;
+ }
+
+ cdc.tr_address = (cdc.tr_address + 2) & 0x7ffff;
+ cdc.tr_source += 2;
+ cdc_length_step();
+ cdc_length_step();
+}
+
+static void cdc_decode(INT32 sector)
+{
+ if (!cdc.dec_enable || !disc_present) return;
+
+ UINT8 m, s, f;
+ msf_from_lba(sector, &m, &s, &f);
+ cdc.head_minute = dec2bcd(m);
+ cdc.head_second = dec2bcd(s);
+ cdc.head_frame = dec2bcd(f);
+ cdc.head_mode = 0x01;
+
+ cdc.dec_valid = 1;
+ cdc.irq_decoder_pending = 1;
+ cdc_poll();
+
+ if (cdc.ctrl_write_request) {
+ cdc.tr_pointer += 2352;
+ cdc.tr_target += 2352;
+
+ // rebuild a raw mode 1 sector, the sync pattern lands at the tail
+ UINT8 raw[2352];
+ memset(raw, 0, sizeof(raw));
+ memset(raw + 1, 0xff, 10);
+ raw[12] = cdc.head_minute;
+ raw[13] = cdc.head_second;
+ raw[14] = cdc.head_frame;
+ raw[15] = cdc.head_mode;
+ if (CDEmuReadDataSector(sector, raw + 16)) {
+ memset(raw + 16, 0, 2048);
+ }
+
+ for (INT32 i = 0; i < 12; i += 2) {
+ CdcRam[((cdc.tr_pointer + i + 2340) >> 1) & 0x1fff] = (raw[i] << 8) | raw[i + 1];
+ }
+ for (INT32 i = 0; i < 2340; i += 2) {
+ CdcRam[((cdc.tr_pointer + i) >> 1) & 0x1fff] = (raw[12 + i] << 8) | raw[13 + i];
+ }
+ }
+}
+
+static UINT8 cdc_read()
+{
+ UINT8 data = 0;
+
+ switch (cdc.address) {
+ case 0x0: // COMIN
+ if (cdc.cmd_empty) { data = 0xff; break; }
+ data = cdc.cmd_fifo[cdc.cmd_read];
+ cdc.cmd_read = (cdc.cmd_read + 1) & 7;
+ if (cdc.cmd_read == cdc.cmd_write) {
+ cdc.cmd_empty = 1;
+ cdc.irq_command_pending = 0;
+ cdc_poll();
+ }
+ break;
+
+ case 0x1: // IFSTAT
+ data = (!cdc.st_active) << 0;
+ data |= (!cdc.tr_active) << 1;
+ data |= (!cdc.st_busy) << 2;
+ data |= (!cdc.tr_busy) << 3;
+ data |= 1 << 4;
+ data |= (!cdc.irq_decoder_pending) << 5;
+ data |= (!cdc.irq_transfer_pending) << 6;
+ data |= (!cdc.irq_command_pending) << 7;
+ break;
+
+ case 0x2: data = cdc.tr_length & 0xff; break; // DBCL
+ case 0x3: data = (cdc.tr_length >> 8) & 0x0f; break; // DBCH
+ case 0x4: data = cdc.ctrl_head ? cdc.sub_file : cdc.head_minute; break;
+ case 0x5: data = cdc.ctrl_head ? cdc.sub_channel : cdc.head_second; break;
+ case 0x6: data = cdc.ctrl_head ? cdc.sub_submode : cdc.head_frame; break;
+ case 0x7: data = cdc.ctrl_head ? cdc.sub_coding : cdc.head_mode; break;
+ case 0x8: data = cdc.tr_pointer & 0xff; break; // PTL
+ case 0x9: data = cdc.tr_pointer >> 8; break; // PTH
+ case 0xa: data = cdc.tr_target & 0xff; break; // WAL
+ case 0xb: data = cdc.tr_target >> 8; break; // WAH
+ case 0xc: data = cdc.dec_enable << 7; break; // STAT0: CRCOK
+ case 0xd: data = 0; break; // STAT1
+ case 0xe: data = (cdc.dec_form << 2) | (cdc.dec_mode << 3); break; // STAT2
+
+ case 0xf: // STAT3
+ data = (!cdc.dec_valid) << 7;
+ cdc.dec_valid = 0;
+ cdc.irq_decoder_pending = 0;
+ cdc_poll();
+ break;
+ }
+
+ // COMIN reads do not increment the address
+ if (cdc.address) cdc.address = (cdc.address + 1) & 0x1f;
+
+ return data;
+}
+
+static void cdc_write(UINT8 data)
+{
+ switch (cdc.address) {
+ case 0x0: // SBOUT
+ if (cdc.st_wait && cdc.tr_busy) break;
+ if (cdc.st_read == cdc.st_write && !cdc.st_empty) cdc.st_read = (cdc.st_read + 1) & 7;
+ cdc.st_fifo[cdc.st_write] = data;
+ cdc.st_write = (cdc.st_write + 1) & 7;
+ cdc.st_empty = 0;
+ cdc.st_active = 1;
+ cdc.st_busy = 1;
+ break;
+
+ case 0x1: // IFCTRL
+ cdc.st_enable = (data >> 0) & 1;
+ cdc.tr_enable = (data >> 1) & 1;
+ cdc.st_wait = !((data >> 2) & 1);
+ cdc.tr_wait = !((data >> 3) & 1);
+ cdc.ctrl_command_break = !((data >> 4) & 1);
+ cdc.irq_decoder_enable = (data >> 5) & 1;
+ cdc.irq_transfer_enable = (data >> 6) & 1;
+ cdc.irq_command_enable = (data >> 7) & 1;
+ cdc_poll();
+ if (!cdc.tr_enable) cdc_transfer_stop();
+ break;
+
+ case 0x2: cdc.tr_length = (cdc.tr_length & 0xf00) | data; break; // DBCL
+ case 0x3: cdc.tr_length = (cdc.tr_length & 0x0ff) | ((data & 0x0f) << 8); break; // DBCH
+ case 0x4: cdc.tr_source = (cdc.tr_source & 0xff00) | data; break; // DACL
+ case 0x5: cdc.tr_source = (cdc.tr_source & 0x00ff) | (data << 8); break; // DACH
+ case 0x6: cdc_transfer_start(); break; // DTRG
+
+ case 0x7: // DTACK
+ cdc.irq_transfer_pending = 0;
+ cdc_poll();
+ break;
+
+ case 0x8: cdc.tr_target = (cdc.tr_target & 0xff00) | data; break; // WAL
+ case 0x9: cdc.tr_target = (cdc.tr_target & 0x00ff) | (data << 8); break; // WAH
+
+ case 0xa: // CTRL0
+ cdc.ctrl_pcode = (data >> 0) & 1;
+ cdc.ctrl_qcode = (data >> 1) & 1;
+ cdc.ctrl_write_request = (data >> 2) & 1;
+ cdc.ctrl_erasure_request = (data >> 3) & 1;
+ cdc.ctrl_auto = (data >> 4) & 1;
+ cdc.ctrl_error = (data >> 5) & 1;
+ cdc.ctrl_edc = (data >> 6) & 1;
+ cdc.dec_enable = (data >> 7) & 1;
+ cdc.dec_mode = cdc.ctrl_mode;
+ cdc.dec_form = cdc.ctrl_form & cdc.ctrl_auto;
+ break;
+
+ case 0xb: // CTRL1
+ cdc.ctrl_head = (data >> 0) & 1;
+ cdc.ctrl_mode_byte_check = (data >> 1) & 1;
+ cdc.ctrl_form = (data >> 2) & 1;
+ cdc.ctrl_mode = (data >> 3) & 1;
+ cdc.ctrl_correction_write = (data >> 4) & 1;
+ cdc.ctrl_descramble = (data >> 5) & 1;
+ cdc.ctrl_sync = (data >> 6) & 1;
+ cdc.ctrl_sync_irq = (data >> 7) & 1;
+ cdc.dec_mode = cdc.ctrl_mode;
+ cdc.dec_form = cdc.ctrl_form & cdc.ctrl_auto;
+ break;
+
+ case 0xc: cdc.tr_pointer = (cdc.tr_pointer & 0xff00) | data; break; // PTL
+ case 0xd: cdc.tr_pointer = (cdc.tr_pointer & 0x00ff) | (data << 8); break; // PTH
+
+ case 0xe: // CTRL2
+ cdc.ctrl_status_trigger = (data >> 0) & 1;
+ cdc.ctrl_status_control = (data >> 1) & 1;
+ cdc.ctrl_erasure_correction = (data >> 2) & 1;
+ break;
+
+ case 0xf: { // RESET
+ UINT8 address = cdc.address;
+ UINT16 stopwatch = cdc.stopwatch;
+ UINT8 tr_destination = cdc.tr_destination;
+ UINT32 tr_address = cdc.tr_address;
+ UINT16 tr_source = cdc.tr_source, tr_target = cdc.tr_target, tr_pointer = cdc.tr_pointer, tr_length = cdc.tr_length;
+ UINT8 tr_completed = cdc.tr_completed;
+ UINT8 st_fifo[8], cmd_fifo[8];
+ memcpy(st_fifo, cdc.st_fifo, 8);
+ memcpy(cmd_fifo, cdc.cmd_fifo, 8);
+ UINT8 st_read = cdc.st_read, st_write = cdc.st_write, st_empty = cdc.st_empty;
+ UINT8 cmd_read = cdc.cmd_read, cmd_write = cdc.cmd_write, cmd_empty = cdc.cmd_empty;
+ UINT8 dec_valid = cdc.dec_valid;
+
+ memset(&cdc, 0, sizeof(cdc));
+
+ cdc.address = address;
+ cdc.stopwatch = stopwatch;
+ cdc.tr_destination = tr_destination;
+ cdc.tr_address = tr_address;
+ cdc.tr_source = tr_source;
+ cdc.tr_target = tr_target;
+ cdc.tr_pointer = tr_pointer;
+ cdc.tr_length = tr_length;
+ cdc.tr_completed = tr_completed;
+ memcpy(cdc.st_fifo, st_fifo, 8);
+ memcpy(cdc.cmd_fifo, cmd_fifo, 8);
+ cdc.st_read = st_read; cdc.st_write = st_write; cdc.st_empty = st_empty;
+ cdc.cmd_read = cmd_read; cdc.cmd_write = cmd_write; cdc.cmd_empty = cmd_empty;
+ cdc.dec_valid = dec_valid;
+
+ cdc.st_wait = 1;
+ cdc.tr_wait = 1;
+ cdc.ctrl_command_break = 1;
+
+ cdc_transfer_stop();
+ cdc_poll();
+ }
+ break;
+ }
+
+ // SBOUT writes do not increment the address
+ if (cdc.address) cdc.address = (cdc.address + 1) & 0x1f;
+}
+
+static void cdc_power()
+{
+ memset(CdcRam, 0, 0x4000);
+ memset(&cdc, 0, sizeof(cdc));
+ cdc.cmd_empty = 1;
+ cdc.st_empty = 1;
+}
+
+// -------------------------------------------------------------------------
+// CDD
+
+static void cdd_update_audio()
+{
+ INT32 want = (cdd.io_status == CDD_PLAYING) && track_is_audio(cdd.io_track);
+
+ if (want) {
+ if (!cdd.audio_playing || (CDEmuGetStatus() != playing && cdd.audio_track != cdd.io_track)) {
+ INT32 aba = cdd.io_sector + 150;
+ if (aba < 0) aba = 0;
+ CDEmuPlay(dec2bcd(aba / (75 * 60)), dec2bcd((aba / 75) % 60), dec2bcd(aba % 75));
+ cdd.audio_playing = 1;
+ cdd.audio_track = cdd.io_track;
+ }
+ } else if (cdd.audio_playing) {
+ CDEmuStop();
+ cdd.audio_playing = 0;
+ }
+}
+
+static void cdd_advance()
+{
+ INT32 track = cdd_in_track(cdd.io_sector + 1);
+ if (track) {
+ cdd.io_track = track;
+ cdd.io_sector++;
+ return;
+ }
+
+ if (cdd.io_sector + 1 < 0) {
+ cdd.io_status = CDD_LEAD_IN;
+ cdd.io_track = 0xa0;
+ } else {
+ cdd.io_status = CDD_LEAD_OUT;
+ cdd.io_track = 0xaa;
+ }
+}
+
+static void cdd_clock()
+{
+ if (++cdd.counter < 434) return;
+ cdd.counter = 0;
+
+ // 75hz
+ if (!cdd.host_clock_enable) return;
+
+ if (cdd.status_pending) {
+ cdd.status_pending = 0;
+ irq_raise(4);
+ }
+
+ switch (cdd.io_status) {
+ case CDD_STOPPED:
+ cdd.io_status = disc_present ? CDD_READING_TOC : CDD_NO_DISC;
+ cdd.io_sector = 0;
+ cdd.io_track = 0;
+ break;
+
+ case CDD_READING_TOC:
+ cdd.io_sector++;
+ if (!cdd_in_lead_in(cdd.io_sector)) {
+ cdd.io_status = CDD_PAUSED;
+ INT32 track = cdd_in_track(cdd.io_sector);
+ if (track) cdd.io_track = track;
+ cdd.io_toc_read = 1;
+ }
+ break;
+
+ case CDD_PLAYING:
+ if (!track_is_audio(cdd.io_track)) {
+ cdc_decode(cdd.io_sector);
+ }
+ cdd_advance();
+ break;
+
+ case CDD_TRACKING:
+ case CDD_SEEKING: {
+ if (cdd.io_latency && --cdd.io_latency) break;
+ cdd.io_status = cdd.io_seeking;
+ INT32 track = cdd_in_track(cdd.io_sector);
+ if (track) cdd.io_track = track;
+ }
+ break;
+ }
+
+ cdd_update_audio();
+}
+
+// normalized position of a sector on the disc surface, for seek latency
+static double cdd_position(INT32 sector)
+{
+ const double sectors = 7500.0 + 330000.0 + 6750.0;
+ const double radius = 0.058 - 0.024;
+ const double inner = 0.024 * 0.024;
+ const double outer = 0.058 * 0.058;
+
+ sector += -7350;
+ double v = sector / sectors * (outer - inner) + inner;
+ if (v < 0.0) v = 0.0;
+
+ return sqrt(v) / radius;
+}
+
+static INT32 cdd_valid()
+{
+ UINT8 sum = 0;
+ for (INT32 i = 0; i < 9; i++) sum += cdd.command[i];
+ return ((~sum) & 0xf) == cdd.command[9];
+}
+
+static void cdd_checksum()
+{
+ UINT8 sum = 0;
+ for (INT32 i = 0; i < 9; i++) sum += cdd.status[i];
+ cdd.status[9] = (~sum) & 0xf;
+}
+
+static void cdd_status_clear(INT32 first)
+{
+ for (INT32 i = first; i < 9; i++) cdd.status[i] = 0;
+}
+
+static void cdd_put_msf(UINT8 m, UINT8 s, UINT8 f)
+{
+ cdd.status[2] = m / 10; cdd.status[3] = m % 10;
+ cdd.status[4] = s / 10; cdd.status[5] = s % 10;
+ cdd.status[6] = f / 10; cdd.status[7] = f % 10;
+}
+
+static void cdd_seek(INT32 seek_status)
+{
+ INT32 m = cdd.command[2] * 10 + cdd.command[3];
+ INT32 s = cdd.command[4] * 10 + cdd.command[5];
+ INT32 f = cdd.command[6] * 10 + cdd.command[7];
+ INT32 lba = msf_to_lba(m, s, f) - 3;
+
+ cdd.counter = 0;
+ cdd.io_status = CDD_SEEKING;
+ cdd.io_seeking = seek_status;
+ cdd.io_latency = (UINT16)(11 + 112.5 * fabs(cdd_position(cdd.io_sector) - cdd_position(lba)));
+ cdd.io_sector = lba;
+
+ cdd.status[1] = 0xf;
+ cdd_status_clear(2);
+}
+
+static void cdd_process()
+{
+ if (!cdd_valid()) {
+ cdd.io_status = CDD_CHECKSUM_ERROR;
+ } else {
+ switch (cdd.command[0]) {
+ case 0x0: // idle
+ if (!cdd.io_latency && cdd.status[1] == 0xf) {
+ cdd.status[1] = 0x2;
+ cdd.status[2] = (cdd.io_track / 10) & 0xf;
+ cdd.status[3] = cdd.io_track % 10;
+ }
+ break;
+
+ case 0x1: // stop
+ cdd.io_status = disc_present ? CDD_STOPPED : CDD_NO_DISC;
+ cdd.status[1] = 0x0;
+ cdd_status_clear(2);
+ break;
+
+ case 0x2: { // request
+ UINT8 m, s, f;
+ switch (cdd.command[3]) {
+ case 0x0: // absolute time
+ msf_from_lba(cdd.io_sector, &m, &s, &f);
+ cdd.status[1] = cdd.command[3];
+ cdd_put_msf(m, s, f);
+ cdd.status[8] = track_is_data(cdd.io_track) << 2;
+ break;
+
+ case 0x1: // relative time
+ msf_from_aba(cdd.io_sector - (track_valid(cdd.io_track) ? toc_lba[cdd.io_track] : 0), &m, &s, &f);
+ cdd.status[1] = cdd.command[3];
+ cdd_put_msf(m, s, f);
+ cdd.status[8] = track_is_data(cdd.io_track) << 2;
+ break;
+
+ case 0x2: // track information
+ cdd.status[1] = cdd.command[3];
+ cdd.status[2] = (cdd.io_track / 10) & 0xf;
+ cdd.status[3] = cdd.io_track % 10;
+ cdd_status_clear(4);
+ if (cdd_in_lead_in(cdd.io_sector)) { cdd.status[2] = 0x0; cdd.status[3] = 0x0; }
+ if (cdd_in_lead_out(cdd.io_sector)) { cdd.status[2] = 0xa; cdd.status[3] = 0xa; }
+ break;
+
+ case 0x3: // disc completion time
+ msf_from_lba(toc_leadout, &m, &s, &f);
+ cdd.status[1] = cdd.command[3];
+ cdd_put_msf(m, s, f);
+ cdd.status[8] = 0x0;
+ break;
+
+ case 0x4: // first and last track numbers
+ cdd.status[1] = cdd.command[3];
+ cdd.status[2] = toc_first / 10; cdd.status[3] = toc_first % 10;
+ cdd.status[4] = toc_last / 10; cdd.status[5] = toc_last % 10;
+ cdd.status[6] = 0x0; cdd.status[7] = 0x0;
+ cdd.status[8] = 0x0;
+ break;
+
+ case 0x5: { // track start time
+ if (cdd.command[4] > 9 || cdd.command[5] > 9) break;
+ INT32 track = cdd.command[4] * 10 + cdd.command[5];
+ if (track_valid(track)) {
+ msf_from_lba(toc_lba[track], &m, &s, &f);
+ } else {
+ m = s = f = 0;
+ }
+ cdd.status[1] = cdd.command[3];
+ cdd_put_msf(m, s, f);
+ if (track_is_data(track)) cdd.status[6] |= 0x8;
+ cdd.status[8] = track % 10;
+ }
+ break;
+
+ case 0x6: // error information
+ cdd.status[1] = cdd.command[3];
+ cdd_status_clear(2);
+ break;
+ }
+ }
+ break;
+
+ case 0x3: cdd_seek(CDD_PLAYING); break; // seek & play
+ case 0x4: cdd_seek(CDD_PAUSED); break; // seek & pause
+
+ case 0x6: // pause
+ if (cdd.io_status == CDD_SEEKING) cdd.io_seeking = CDD_PAUSED;
+ else cdd.io_status = CDD_PAUSED;
+ break;
+
+ case 0x7: // play
+ if (cdd.io_status == CDD_SEEKING) cdd.io_seeking = CDD_PLAYING;
+ else cdd.io_status = CDD_PLAYING;
+ break;
+
+ case 0xa: // track skip, handled as a null seek
+ cdd.counter = 0;
+ cdd.io_status = CDD_TRACKING;
+ cdd.io_seeking = CDD_PAUSED;
+ cdd.io_latency = 0;
+ cdd.status[1] = 0xf;
+ cdd_status_clear(2);
+ break;
+
+ default:
+ cdd.io_status = CDD_COMMAND_ERROR;
+ break;
+ }
+ }
+
+ cdd.status[0] = cdd.io_status;
+ cdd_checksum();
+ cdd.status_pending = 1;
+
+ cdd_update_audio();
+}
+
+static void cdd_insert()
+{
+ if (!disc_present) {
+ cdd.io_status = CDD_NO_DISC;
+ return;
+ }
+
+ // start right before the pregap so that reading the toc completes quickly
+ cdd.io_status = CDD_READING_TOC;
+ cdd.io_sector = -151;
+ cdd.io_track = 0;
+}
+
+static void cdd_power()
+{
+ irq_mask &= ~0x10;
+ irq_lower(4);
+
+ if (cdd.audio_playing) CDEmuStop();
+
+ memset(&cdd, 0, sizeof(cdd));
+ cdd.io_status = CDD_NO_DISC;
+ cdd.dac_attenuator = 0x4000;
+ cdd.dac_attenuated = 0x4000;
+
+ cdd_insert();
+ cdd_checksum();
+}
+
+// -------------------------------------------------------------------------
+// timer
+
+static void timer_clock()
+{
+ if (timer_frequency == 0) return;
+
+ if (timer_counter == 0) {
+ timer_counter = timer_frequency;
+ irq_raise(3);
+ } else {
+ timer_counter--;
+ }
+}
+
+// -------------------------------------------------------------------------
+// graphics asic
+
+static inline UINT8 gpu_read(UINT32 address)
+{
+ address &= 0x7ffff;
+ INT32 shift = 12 - ((address & 3) << 2);
+ return (WordRam[address >> 2] >> shift) & 0xf;
+}
+
+static inline void gpu_write(UINT32 address, UINT8 data)
+{
+ address &= 0x7ffff;
+ INT32 shift = 12 - ((address & 3) << 2);
+ UINT16 *p = &WordRam[address >> 2];
+ *p = (*p & ~(0xf << shift)) | ((data & 0xf) << shift);
+}
+
+static void gpu_render(UINT32 address, INT32 width)
+{
+ INT32 stamp_shift = 4;
+ INT32 map_shift = 4 << gpu.stamp_map_size;
+ UINT32 index_mask = 0x7ff;
+ UINT32 pixel_mask = 0x0f;
+
+ if (gpu.stamp_tile_size) {
+ stamp_shift++;
+ map_shift--;
+ index_mask &= ~3;
+ pixel_mask |= 0x10;
+ }
+
+ UINT32 image_width = (gpu.image_vcells + 1) << 6;
+ UINT32 map_mask = (!gpu.stamp_map_size) ? 0x07ffff : 0x7fffff;
+
+ UINT32 x = (WordRam[gpu.vector_address] << 8) & 0xffffff;
+ gpu.vector_address = (gpu.vector_address + 1) & 0x1ffff;
+ UINT32 y = (WordRam[gpu.vector_address] << 8) & 0xffffff;
+ gpu.vector_address = (gpu.vector_address + 1) & 0x1ffff;
+ INT16 xstep = (INT16)WordRam[gpu.vector_address];
+ gpu.vector_address = (gpu.vector_address + 1) & 0x1ffff;
+ INT16 ystep = (INT16)WordRam[gpu.vector_address];
+ gpu.vector_address = (gpu.vector_address + 1) & 0x1ffff;
+
+ while (width--) {
+ if (gpu.stamp_repeat) {
+ x &= map_mask;
+ y &= map_mask;
+ }
+
+ UINT8 output = 0;
+ if (((x | y) & ~map_mask & 0xffffff) == 0) {
+ UINT32 xtrunc = x >> 11;
+ UINT32 ytrunc = y >> 11;
+ UINT32 xstamp = xtrunc >> stamp_shift;
+ UINT32 ystamp = ytrunc >> stamp_shift;
+
+ UINT16 map_entry = WordRam[(gpu.stamp_map_address + (ystamp << map_shift) + xstamp) & 0x1ffff];
+ UINT32 index = map_entry & index_mask;
+ INT32 lroll = (map_entry >> 13) & 1;
+ INT32 hroll = (map_entry >> 14) & 1;
+ INT32 hflip = (map_entry >> 15) & 1;
+
+ if (index) {
+ if (hflip) { xtrunc = ~xtrunc; }
+ if (hroll) { xtrunc = ~xtrunc; ytrunc = ~ytrunc; }
+ if (lroll) { UINT32 t = xtrunc; xtrunc = ~ytrunc; ytrunc = t; }
+
+ UINT32 xpixel = xtrunc & pixel_mask;
+ UINT32 ypixel = ytrunc & pixel_mask;
+
+ output = gpu_read((index << 8) | ((xpixel & ~7) << stamp_shift) | (ypixel << 3) | (xpixel & 7));
+ }
+ }
+
+ UINT8 input = gpu_read(address);
+ switch (wram_priority) {
+ case 1: output = input ? input : output; break;
+ case 2: output = output ? output : input; break;
+ case 3: output = input; break;
+ }
+ gpu_write(address, output);
+
+ address = (address + 1) & 0x7ffff;
+ if (!(address & 7)) address = (address + image_width - 8) & 0x7ffff;
+
+ x = (x + xstep) & 0xffffff;
+ y = (y + ystep) & 0xffffff;
+ }
+}
+
+static void gpu_start()
+{
+ if (wram_mode) return; // 2Mbit mode only
+
+ gpu.active = 1;
+ gpu.period = 5 * gpu.image_hdots;
+ if (gpu.period == 0) gpu.period = 1;
+ gpu.counter = 0;
+
+ gpu.image_address = ((gpu.image_base << 1) + gpu.image_offset) & 0x7ffff;
+ gpu.vector_address = (gpu.vector_base >> 1) & 0x1ffff;
+ gpu.stamp_map_address = gpu.stamp_map_base >> 1;
+
+ if (gpu.stamp_map_size == 0 && gpu.stamp_tile_size == 0) gpu.stamp_map_address &= 0x1ff00;
+ if (gpu.stamp_map_size == 0 && gpu.stamp_tile_size == 1) gpu.stamp_map_address &= 0x1ffc0;
+ if (gpu.stamp_map_size == 1 && gpu.stamp_tile_size == 0) gpu.stamp_map_address &= 0x10000;
+ if (gpu.stamp_map_size == 1 && gpu.stamp_tile_size == 1) gpu.stamp_map_address &= 0x1c000;
+}
+
+static void gpu_step(INT32 clocks)
+{
+ if (!gpu.active) return;
+
+ gpu.counter += clocks;
+ while (gpu.counter >= gpu.period) {
+ gpu.counter -= gpu.period;
+ gpu_render(gpu.image_address, gpu.image_hdots);
+ gpu.image_address = (gpu.image_address + 8) & 0x7ffff;
+ gpu.image_vdots--;
+ if (gpu.image_vdots == 0) {
+ gpu.active = 0;
+ irq_raise(1);
+ break;
+ }
+ }
+}
+
+// -------------------------------------------------------------------------
+// pcm
+
+static void pcm_clock()
+{
+ INT32 left = 0;
+ INT32 right = 0;
+
+ if (pcm_enable) {
+ for (INT32 i = 0; i < 8; i++) {
+ scd_pcm_channel *ch = &pcm_ch[i];
+ if (!ch->enable) continue;
+
+ INT32 data = PcmRam[(ch->address >> 11) & 0xffff];
+ if (data == 0xff) { // loop
+ ch->address = ch->loop << 11;
+ data = PcmRam[ch->loop];
+ if (data == 0xff) continue;
+ } else {
+ ch->address = (ch->address + ch->step) & 0x7ffffff;
+ }
+
+ data = (data & 0x80) ? (data & 0x7f) : -(data & 0x7f);
+
+ left += (data * ch->envelope * (ch->pan & 0x0f)) >> 5;
+ right += (data * ch->envelope * (ch->pan >> 4)) >> 5;
+ }
+ }
+
+ // 10-bit dac
+ if (left < -32768) left = -32768; else if (left > 32767) left = 32767;
+ if (right < -32768) right = -32768; else if (right > 32767) right = 32767;
+ left = (left >> 2) & ~15;
+ right = (right >> 2) & ~15;
+
+ INT32 pos = (pcm_fifo_read + pcm_fifo_count) % PCM_FIFO_SIZE;
+ pcm_fifo[pos][0] = left;
+ pcm_fifo[pos][1] = right;
+ if (pcm_fifo_count < PCM_FIFO_SIZE) {
+ pcm_fifo_count++;
+ } else {
+ pcm_fifo_read = (pcm_fifo_read + 1) % PCM_FIFO_SIZE;
+ }
+}
+
+static void pcm_stream_update(INT16 **streams, INT32 samples)
+{
+ for (INT32 i = 0; i < samples; i++) {
+ if (pcm_fifo_count) {
+ pcm_last[0] = pcm_fifo[pcm_fifo_read][0];
+ pcm_last[1] = pcm_fifo[pcm_fifo_read][1];
+ pcm_fifo_read = (pcm_fifo_read + 1) % PCM_FIFO_SIZE;
+ pcm_fifo_count--;
+ }
+ streams[0][i] = pcm_last[0];
+ streams[1][i] = pcm_last[1];
+ }
+}
+
+static UINT8 pcm_read(UINT32 index)
+{
+ if (index >= 0x1000 && index <= 0x1fff) {
+ return PcmRam[(pcm_bank << 12) | (index & 0xfff)];
+ }
+
+ if (index >= 0x0010 && index <= 0x001f) {
+ UINT16 offset = pcm_ch[(index >> 1) & 7].address >> 11;
+ return (index & 1) ? (offset >> 8) : (offset & 0xff);
+ }
+
+ return 0xff;
+}
+
+static void pcm_write(UINT32 index, UINT8 data)
+{
+ if (index >= 0x1000 && index <= 0x1fff) {
+ PcmRam[(pcm_bank << 12) | (index & 0xfff)] = data;
+ return;
+ }
+
+ scd_pcm_channel *ch = &pcm_ch[pcm_channel];
+
+ switch (index) {
+ case 0x0: ch->envelope = data; break;
+ case 0x1: ch->pan = data; break;
+ case 0x2: ch->step = (ch->step & 0xff00) | data; break;
+ case 0x3: ch->step = (ch->step & 0x00ff) | (data << 8); break;
+ case 0x4: ch->loop = (ch->loop & 0xff00) | data; break;
+ case 0x5: ch->loop = (ch->loop & 0x00ff) | (data << 8); break;
+
+ case 0x6:
+ ch->start = data;
+ if (!ch->enable) ch->address = (ch->start << 8) << 11;
+ break;
+
+ case 0x7:
+ if (data & 0x40) {
+ pcm_channel = data & 7;
+ } else {
+ pcm_bank = data & 0x0f;
+ }
+ pcm_enable = (data >> 7) & 1;
+ break;
+
+ case 0x8:
+ for (INT32 i = 0; i < 8; i++) {
+ pcm_ch[i].enable = !(data & 1);
+ data >>= 1;
+ if (!pcm_ch[i].enable) pcm_ch[i].address = (pcm_ch[i].start << 8) << 11;
+ }
+ break;
+ }
+}
+
+static void pcm_power()
+{
+ memset(PcmRam, 0, 0x10000);
+ pcm_enable = pcm_bank = pcm_channel = 0;
+ memset(pcm_ch, 0, sizeof(pcm_ch));
+ for (INT32 i = 0; i < 8; i++) pcm_ch[i].pan = 0xff;
+
+ pcm_fifo_read = pcm_fifo_count = 0;
+ pcm_last[0] = pcm_last[1] = 0;
+}
+
+// -------------------------------------------------------------------------
+// sub cpu i/o (gate array)
+
+static UINT16 sub_io_read(UINT32 a, INT32 upper, INT32 lower)
+{
+ a = 0xff8000 | (a & 0x1fe);
+
+ switch (a) {
+ case 0xff8000:
+ return 1 | (led_status << 8); // peripheral ready, version 0
+
+ case 0xff8002: {
+ UINT16 d = (!wram_mode) ? !wram_switch : wram_select;
+ d |= ((!wram_mode) ? wram_switch : wram_switch_request) << 1;
+ d |= wram_mode << 2;
+ d |= wram_priority << 3;
+ d |= pram_protect << 8;
+ return d;
+ }
+
+ case 0xff8004:
+ return cdc.address | (cdc.tr_destination << 8) | (cdc.tr_ready << 14) | (cdc.tr_completed << 15);
+
+ case 0xff8006:
+ return lower ? cdc_read() : 0;
+
+ case 0xff8008:
+ return cdc_transfer_read();
+
+ case 0xff800a:
+ return (cdc.tr_address >> 3) & 0xffff;
+
+ case 0xff800c:
+ return cdc.stopwatch & 0xfff;
+
+ case 0xff800e:
+ return (comm_cfm << 8) | comm_cfs;
+
+ case 0xff8030:
+ return timer_frequency;
+
+ case 0xff8032:
+ return irq_mask & 0x7e;
+
+ case 0xff8034:
+ return 0; // fade data transfer complete
+
+ case 0xff8036:
+ return (cdd.host_clock_enable << 2) | (track_is_data(cdd.io_track) << 8);
+
+ case 0xff804c:
+ return gpu.font_bg | (gpu.font_fg << 4);
+
+ case 0xff804e:
+ return gpu.font_data;
+
+ case 0xff8058:
+ return gpu.stamp_repeat | (gpu.stamp_tile_size << 1) | (gpu.stamp_map_size << 2) | (gpu.active << 15);
+
+ case 0xff805a:
+ return ((gpu.stamp_map_base >> 9) & 0x1ff) << 7;
+
+ case 0xff805c:
+ return gpu.image_vcells;
+
+ case 0xff805e:
+ return ((gpu.image_base >> 5) & 0x1fff) << 3;
+
+ case 0xff8060:
+ return gpu.image_offset;
+
+ case 0xff8062:
+ return gpu.image_hdots;
+
+ case 0xff8064:
+ return gpu.image_vdots;
+
+ case 0xff8066:
+ return ((gpu.vector_base >> 3) & 0x7fff) << 1;
+
+ case 0xff8068:
+ return 0; // subcode address
+ }
+
+ if (a >= 0xff8010 && a <= 0xff801f) return comm_command[(a - 0xff8010) >> 1];
+ if (a >= 0xff8020 && a <= 0xff802f) return comm_status[(a - 0xff8020) >> 1];
+
+ if (a >= 0xff8038 && a <= 0xff8041) {
+ UINT32 index = a - 0xff8038;
+ return cdd.status[index | 1] | (cdd.status[index] << 8);
+ }
+
+ if (a >= 0xff8042 && a <= 0xff804b) {
+ UINT32 index = a - 0xff8042;
+ return cdd.command[index | 1] | (cdd.command[index] << 8);
+ }
+
+ if (a >= 0xff8050 && a <= 0xff8057) {
+ INT32 index = 12 - ((a - 0xff8050) << 1);
+ UINT16 d = 0;
+ for (INT32 i = 0; i < 4; i++) {
+ UINT8 c = (gpu.font_data & (1 << (index | i))) ? gpu.font_fg : gpu.font_bg;
+ d |= c << (i * 4);
+ }
+ return d;
+ }
+
+ return 0; // includes subcode buffer (not emulated)
+}
+
+static void sub_io_write(UINT32 a, UINT16 d, INT32 upper, INT32 lower)
+{
+ a = 0xff8000 | (a & 0x1fe);
+
+ switch (a) {
+ case 0xff8000:
+ if (lower && !(d & 1)) cdd_power(); // peripheral reset
+ if (upper) led_status = (d >> 8) & 3;
+ return;
+
+ case 0xff8002: { // 8-bit register, writes go to the low byte
+ UINT8 v = d & 0xff;
+ if (wram_select != (v & 1)) wram_switch_request = 0;
+ wram_select = v & 1;
+ wram_mode = (v >> 2) & 1;
+ wram_priority = (v >> 3) & 3;
+ if (wram_select) wram_switch = 0;
+ }
+ return;
+
+ case 0xff8004:
+ if (lower) cdc.address = d & 0x1f;
+ if (upper) cdc.tr_destination = (d >> 8) & 7;
+ return;
+
+ case 0xff8006:
+ if (lower) cdc_write(d & 0xff);
+ return;
+
+ case 0xff800a:
+ cdc.tr_address = (d & 0xffff) << 3;
+ return;
+
+ case 0xff800c:
+ cdc.stopwatch = 0;
+ return;
+
+ case 0xff800e:
+ comm_cfs = d & 0xff;
+ return;
+
+ case 0xff8030:
+ timer_counter = timer_frequency = d & 0xff;
+ return;
+
+ case 0xff8032:
+ if (lower) irq_mask = d & 0x7e;
+ return;
+
+ case 0xff8034:
+ cdd.dac_rate = (d >> 1) & 1;
+ cdd.dac_deemphasis = (d >> 2) & 3;
+ cdd.dac_attenuator = d & 0xffc0;
+ return;
+
+ case 0xff8036:
+ if (lower) {
+ if (!cdd.host_clock_enable && (d & 4)) irq_raise(4);
+ cdd.host_clock_enable = (d >> 2) & 1;
+ cdd.counter = 0;
+ }
+ return;
+
+ case 0xff804c:
+ gpu.font_bg = d & 0xf;
+ gpu.font_fg = (d >> 4) & 0xf;
+ return;
+
+ case 0xff804e:
+ if (lower) gpu.font_data = (gpu.font_data & 0xff00) | (d & 0x00ff);
+ if (upper) gpu.font_data = (gpu.font_data & 0x00ff) | (d & 0xff00);
+ return;
+
+ case 0xff8058:
+ if (lower) {
+ gpu.stamp_repeat = d & 1;
+ gpu.stamp_tile_size = (d >> 1) & 1;
+ gpu.stamp_map_size = (d >> 2) & 1;
+ }
+ return;
+
+ case 0xff805a:
+ gpu.stamp_map_base = (gpu.stamp_map_base & 0x7f) | (((d >> 5) & 0x7ff) << 7);
+ return;
+
+ case 0xff805c:
+ gpu.image_vcells = d & 0x1f;
+ return;
+
+ case 0xff805e:
+ gpu.image_base = (gpu.image_base & 0x1f) | (((d >> 3) & 0x1fff) << 5);
+ return;
+
+ case 0xff8060:
+ gpu.image_offset = d & 0x3f;
+ return;
+
+ case 0xff8062:
+ gpu.image_hdots = d & 0x1ff;
+ return;
+
+ case 0xff8064:
+ gpu.image_vdots = d & 0xff;
+ return;
+
+ case 0xff8066:
+ gpu.vector_base = (gpu.vector_base & 0x7) | (((d >> 1) & 0x7fff) << 3);
+ gpu_start();
+ return;
+ }
+
+ if (a >= 0xff8020 && a <= 0xff802f) {
+ UINT16 *p = &comm_status[(a - 0xff8020) >> 1];
+ if (lower) *p = (*p & 0xff00) | (d & 0x00ff);
+ if (upper) *p = (*p & 0x00ff) | (d & 0xff00);
+ return;
+ }
+
+ if (a >= 0xff8042 && a <= 0xff804b) {
+ UINT32 index = a - 0xff8042;
+ if (lower) cdd.command[index | 1] = d & 0xf;
+ if (upper) cdd.command[index] = (d >> 8) & 0xf;
+ if (lower && (index | 1) == 9) cdd_process();
+ return;
+ }
+}
+
+// -------------------------------------------------------------------------
+// sub cpu bus
+
+// 2M word ram owned by the main cpu: the sub cpu bus is held (/DTACK) until the main cpu
+// gives it back (Mortal Kombat). The instruction runs to its end, then is replayed once released.
+static UINT32 wait_regs[16];
+static UINT32 wait_pc;
+static UINT32 wait_sr;
+
+static void wait_revert(INT32 mode, INT32 reg, INT32 size)
+{
+ if (size == 1 && reg == 7) size = 2;
+ if (mode == 3) wait_regs[8 + reg] -= size; // (An)+
+ if (mode == 4) wait_regs[8 + reg] += size; // -(An)
+}
+
+static void sub_wram_block(UINT32 address, INT32 write)
+{
+ if (sub_wram_wait || SekGetActive() != 1) return;
+
+ for (INT32 i = 0; i < 16; i++)
+ wait_regs[i] = m68k_get_reg(NULL, (m68k_register_t)(M68K_REG_D0 + i));
+ wait_pc = m68k_get_reg(NULL, M68K_REG_PPC);
+ wait_sr = m68k_get_reg(NULL, M68K_REG_SR);
+
+ // undo the address register updates already done by the instruction
+ UINT32 ir = m68k_get_reg(NULL, M68K_REG_IR);
+ if (ir >= 0x1000 && ir < 0x4000) { // move
+ static const INT32 move_size[4] = { 0, 1, 4, 2 };
+ INT32 size = move_size[(ir >> 12) & 3];
+ wait_revert((ir >> 3) & 7, ir & 7, size);
+ if (write) wait_revert((ir >> 6) & 7, (ir >> 9) & 7, size);
+ } else if ((ir & 0xfb80) != 0x4880) { // movem updates the register after the transfer
+ INT32 size = 1 << ((ir >> 6) & 3);
+ if ((ir & 0xb0c0) == 0x90c0 || (ir & 0xf0c0) == 0xb0c0) size = (ir & 0x100) ? 4 : 2; // adda, suba, cmpa
+ if ((ir & 0xf100) == 0x0100 || (ir & 0xff00) == 0x0800 || (ir & 0xffc0) == 0x4ac0 || (ir & 0xf0c0) == 0x50c0) size = 1; // bit ops, tas, scc
+ if (size == 8) size = 2;
+ wait_revert((ir >> 3) & 7, ir & 7, size);
+ }
+
+ sub_wram_wait = 1;
+ SekRunEnd();
+}
+
+static void sub_wram_replay()
+{
+ m68k_set_reg(M68K_REG_SR, wait_sr);
+ for (INT32 i = 0; i < 16; i++)
+ m68k_set_reg((m68k_register_t)(M68K_REG_D0 + i), wait_regs[i]);
+ m68k_set_reg(M68K_REG_PC, wait_pc);
+}
+
+static UINT16 sub_read16(UINT32 a, INT32 upper, INT32 lower)
+{
+ a &= 0xffffff;
+
+ if (a < 0x080000) {
+ return prg_read_word(a);
+ }
+
+ if (a < 0x0c0000) {
+ if (wram_mode == 0) {
+ if (wram_switch == 0) {
+ sub_wram_block(a, 0);
+ return 0xffff;
+ }
+ return WordRam[(a >> 1) & 0x1ffff];
+ }
+
+ // 1Mbit mode: dot-mapped view, one nibble per byte
+ INT32 ofs = (a & 2) ? 0 : 8;
+ UINT16 w = WordRam[((a >> 1) & 0x1fffe) | (wram_select ^ 1)];
+ return (((w >> (4 + ofs)) & 0xf) << 8) | ((w >> ofs) & 0xf);
+ }
+
+ if (a < 0x0e0000) {
+ if (wram_mode == 1) return WordRam[(a & 0x1fffe) | (wram_select ^ 1)];
+ return 0;
+ }
+
+ if (a >= 0xfe0000 && a <= 0xfeffff) {
+ return lower ? BackupRam[(a >> 1) & 0x1fff] : 0;
+ }
+
+ if (a >= 0xff0000 && a <= 0xff7fff) {
+ return lower ? pcm_read((a >> 1) & 0x1fff) : 0;
+ }
+
+ if (a >= 0xff8000) {
+ return sub_io_read(a, upper, lower);
+ }
+
+ return 0;
+}
+
+static void sub_write16(UINT32 a, UINT16 d, INT32 upper, INT32 lower)
+{
+ a &= 0xffffff;
+
+ if (a < 0x080000) {
+ if (a >= ((UINT32)pram_protect << 9)) prg_write(a, d, upper, lower);
+ return;
+ }
+
+ if (a < 0x0c0000) {
+ if (wram_mode == 0) {
+ if (wram_switch == 0) {
+ sub_wram_block(a, 1);
+ return;
+ }
+ wram_write((a >> 1) & 0x1ffff, d, upper, lower);
+ return;
+ }
+
+ // 1Mbit mode: dot-mapped view, with priority modes
+ INT32 ofs = (a & 2) ? 0 : 8;
+ UINT32 idx = ((a >> 1) & 0x1fffe) | (wram_select ^ 1);
+ UINT16 w = WordRam[idx];
+ UINT16 hi = (w >> (4 + ofs)) & 0xf;
+ UINT16 lo = (w >> ofs) & 0xf;
+ UINT16 dhi = (d >> 8) & 0xf;
+ UINT16 dlo = d & 0xf;
+
+ switch (wram_priority) {
+ case 0:
+ if (upper) hi = dhi;
+ if (lower) lo = dlo;
+ break;
+
+ case 1: // under write
+ if (upper && !hi) hi = dhi;
+ if (lower && !lo) lo = dlo;
+ break;
+
+ case 2: // over write
+ if (upper && dhi) hi = dhi;
+ if (lower && dlo) lo = dlo;
+ break;
+
+ default:
+ return;
+ }
+
+ WordRam[idx] = (w & ~(0xff << ofs)) | (hi << (4 + ofs)) | (lo << ofs);
+ return;
+ }
+
+ if (a < 0x0e0000) {
+ if (wram_mode == 1) wram_write((a & 0x1fffe) | (wram_select ^ 1), d, upper, lower);
+ return;
+ }
+
+ if (a >= 0xfe0000 && a <= 0xfeffff) {
+ if (lower) BackupRam[(a >> 1) & 0x1fff] = d & 0xff;
+ return;
+ }
+
+ if (a >= 0xff0000 && a <= 0xff7fff) {
+ if (lower) pcm_write((a >> 1) & 0x1fff, d & 0xff);
+ return;
+ }
+
+ if (a >= 0xff8000) {
+ sub_io_write(a, d, upper, lower);
+ }
+}
+
+static UINT8 __fastcall sub_read_byte(UINT32 a)
+{
+ UINT16 d = sub_read16(a & ~1, !(a & 1), a & 1);
+ return (a & 1) ? (d & 0xff) : (d >> 8);
+}
+
+static UINT16 __fastcall sub_read_word(UINT32 a)
+{
+ return sub_read16(a, 1, 1);
+}
+
+static void __fastcall sub_write_byte(UINT32 a, UINT8 d)
+{
+ sub_write16(a & ~1, (d << 8) | d, !(a & 1), a & 1);
+}
+
+static void __fastcall sub_write_word(UINT32 a, UINT16 d)
+{
+ sub_write16(a, d, 1, 1);
+}
+
+// -------------------------------------------------------------------------
+// main cpu bus
+
+static UINT16 main_read16(UINT32 a)
+{
+ a &= 0x3fffff;
+ a &= ~0x1c0000; // mirrors
+
+ if (a < 0x020000) {
+ if (a == 0x70) return vector_level4 >> 16;
+ if (a == 0x72) return vector_level4 & 0xffff;
+ return BURN_ENDIAN_SWAP_INT16(*((UINT16*)(BiosRom + (a & 0x1fffe))));
+ }
+
+ if (a < 0x040000) {
+ if (!sub_request) return 0xffff;
+ return prg_read_word((pram_bank << 17) | (a & 0x1fffe));
+ }
+
+ if (a >= 0x200000 && a < 0x240000) {
+ UINT32 idx;
+ if (wram_mode == 0) {
+ if (wram_switch == 1) return 0xffff;
+ idx = (a >> 1) & 0x1ffff;
+ } else {
+ idx = wram_main_1m_index(a);
+ }
+
+ return WordRam[idx];
+ }
+
+ return 0xffff;
+}
+
+static void main_write16(UINT32 a, UINT16 d, INT32 upper, INT32 lower)
+{
+ a &= 0x3fffff;
+ a &= ~0x1c0000;
+
+ if (a >= 0x020000 && a < 0x040000) {
+ if (!sub_request) return;
+ prg_write((pram_bank << 17) | (a & 0x1fffe), d, upper, lower);
+ return;
+ }
+
+ if (a >= 0x200000 && a < 0x240000) {
+ UINT32 idx;
+ if (wram_mode == 0) {
+ if (wram_switch == 1) return;
+ idx = (a >> 1) & 0x1ffff;
+ } else {
+ idx = wram_main_1m_index(a);
+ }
+ wram_write(idx, d, upper, lower);
+ }
+}
+
+static UINT8 __fastcall main_read_byte(UINT32 a)
+{
+ UINT16 d = main_read16(a & ~1);
+ return (a & 1) ? (d & 0xff) : (d >> 8);
+}
+
+static UINT16 __fastcall main_read_word(UINT32 a)
+{
+ return main_read16(a);
+}
+
+static void __fastcall main_write_byte(UINT32 a, UINT8 d)
+{
+ main_write16(a & ~1, (d << 8) | d, !(a & 1), a & 1);
+}
+
+static void __fastcall main_write_word(UINT32 a, UINT16 d)
+{
+ main_write16(a, d, 1, 1);
+}
+
+static void sub_run_cycles(INT32 cycles); // forward
+
+// the main cpu runs ahead of the sub cpu: bring the sub cpu up to date before the main cpu
+// touches the gate array, otherwise handshakes (word ram swaps, comm flags) can deadlock.
+// A write only lands at the end of the main cpu instruction, the sub cpu must not see it
+// before (Dune: word ram 1M swap request answered too early, then requested twice)
+#define MAIN_WRITE_DELAY 16
+
+static void sync_sub(INT32 main_extra)
+{
+ if (SegaCDMainCycles == NULL || SekGetActive() != 0) return;
+
+ INT32 target = (INT64)(SegaCDMainCycles() + main_extra) * SCD_CLOCK / main_clock;
+ if (target <= sub_cycles_frame) return;
+
+ SekClose();
+ SekOpen(1);
+ sub_run_cycles(target - SekTotalCycles());
+ SekClose();
+ SekOpen(0);
+}
+
+// main cpu side of the gate array (a12000-a1203f, mirrored up to a120ff)
+static UINT16 main_io_read(UINT32 a)
+{
+ if ((a & 0xffff00) != 0xa12000) return 0xffff;
+ sync_sub(0);
+ a = 0xa12000 | (a & 0x3e);
+
+ switch (a) {
+ case 0xa12000:
+ return sub_run | (sub_request << 1) | (((irq_pending >> 2) & 1) << 8) | (((irq_mask >> 2) & 1) << 15);
+
+ case 0xa12002: {
+ UINT16 d = (!wram_mode) ? !wram_switch : wram_select;
+ d |= ((!wram_mode) ? wram_switch : wram_switch_request) << 1;
+ d |= wram_mode << 2;
+ d |= pram_bank << 6;
+ d |= pram_protect << 8;
+ return d;
+ }
+
+ case 0xa12004:
+ return (cdc.tr_destination << 8) | (cdc.tr_ready << 14) | (cdc.tr_completed << 15);
+
+ case 0xa12006:
+ return vector_level4 & 0xffff;
+
+ case 0xa12008:
+ return (cdc.tr_destination == 2) ? cdc_transfer_read() : 0;
+
+ case 0xa1200c:
+ return cdc.stopwatch & 0xfff;
+
+ case 0xa1200e:
+ return (comm_cfm << 8) | comm_cfs;
+ }
+
+ if (a >= 0xa12010 && a <= 0xa1201f) return comm_command[(a - 0xa12010) >> 1];
+ if (a >= 0xa12020 && a <= 0xa1202f) return comm_status[(a - 0xa12020) >> 1];
+
+ return 0;
+}
+
+static void main_io_write(UINT32 a, UINT16 d, INT32 upper, INT32 lower)
+{
+ if ((a & 0xffff00) != 0xa12000) return;
+ sync_sub(MAIN_WRITE_DELAY);
+ a = 0xa12000 | (a & 0x3e);
+
+ switch (a) {
+ case 0xa12000:
+ if (lower) {
+ if (sub_run && !(d & 1)) sub_reset_pending = 1;
+ sub_run = d & 1;
+ sub_request = sub_run ? ((d >> 1) & 1) : 1; // reset forces a bus request
+ sub_halt = sub_request;
+ }
+ if (upper) {
+ if (d & 0x100) irq_raise(2);
+ }
+ return;
+
+ case 0xa12002:
+ if (lower) {
+ if ((d & 2) == 0) wram_switch_request = 1;
+ else wram_switch = 1;
+ pram_bank = (d >> 6) & 3;
+ }
+ if (upper) pram_protect = d >> 8;
+ return;
+
+ case 0xa12006:
+ if (upper) vector_level4 = (vector_level4 & 0xffff00ff) | (d & 0xff00);
+ if (lower) vector_level4 = (vector_level4 & 0xffffff00) | (d & 0x00ff);
+ return;
+
+ case 0xa1200e: // 8-bit register, writes go to the high byte
+ comm_cfm = d >> 8;
+ return;
+ }
+
+ if (a >= 0xa12010 && a <= 0xa1201f) {
+ UINT16 *p = &comm_command[(a - 0xa12010) >> 1];
+ if (lower) *p = (*p & 0xff00) | (d & 0x00ff);
+ if (upper) *p = (*p & 0x00ff) | (d & 0xff00);
+ }
+}
+
+// ram cartridge area (not emulated): writes are ignored, reads stay unmapped
+static void __fastcall main_cart_write_byte(UINT32, UINT8) { }
+static void __fastcall main_cart_write_word(UINT32, UINT16) { }
+
+static UINT8 __fastcall main_io_read_byte(UINT32 a)
+{
+ UINT16 d = main_io_read(a & ~1);
+ return (a & 1) ? (d & 0xff) : (d >> 8);
+}
+
+static UINT16 __fastcall main_io_read_word(UINT32 a)
+{
+ return main_io_read(a);
+}
+
+static void __fastcall main_io_write_byte(UINT32 a, UINT8 d)
+{
+ main_io_write(a & ~1, (d << 8) | d, !(a & 1), a & 1);
+}
+
+static void __fastcall main_io_write_word(UINT32 a, UINT16 d)
+{
+ main_io_write(a, d, 1, 1);
+}
+
+// -------------------------------------------------------------------------
+// sub cpu execution
+
+static void peripherals_step(INT32 clocks)
+{
+ gpu_step(clocks);
+
+ counter_divider += clocks;
+ while (counter_divider >= 384) {
+ counter_divider -= 384;
+ cdc.stopwatch = (cdc.stopwatch + 1) & 0xfff;
+ cdd_clock();
+ timer_clock();
+ pcm_clock();
+ }
+
+ counter_dma += clocks;
+ if (!cdc.tr_active) {
+ counter_dma %= 6;
+ return;
+ }
+ while (counter_dma >= 6) {
+ counter_dma -= 6;
+ cdc_transfer_dma();
+ }
+}
+
+static void sub_run_cycles(INT32 cycles)
+{
+ while (cycles > 0) {
+ if (irq_dirty) sub_irq_update();
+
+ INT32 runnable = sub_run && !sub_halt;
+ if (runnable && sub_reset_pending) {
+ // the reset vectors are fetched once the cpu is released
+ SekReset();
+ sub_reset_pending = 0;
+ sub_wram_wait = 0;
+ }
+
+ if (sub_wram_wait == 2 && (wram_mode == 1 || wram_switch == 1)) sub_wram_wait = 0;
+
+ INT32 done;
+ if (runnable && !sub_wram_wait) {
+ done = SekRun(cycles);
+ if (done <= 0) done = cycles;
+ if (sub_wram_wait == 1) {
+ sub_wram_replay();
+ sub_wram_wait = 2;
+ }
+ } else {
+ SekIdle(cycles);
+ done = cycles;
+ }
+
+ peripherals_step(done);
+ cycles -= done;
+ }
+
+ sub_cycles_frame = SekTotalCycles();
+}
+
+void SegaCDRunSub(INT32 main_cycles)
+{
+ INT32 target = (INT64)main_cycles * SCD_CLOCK / main_clock;
+ if (target <= sub_cycles_frame) return;
+
+ SekClose();
+ SekOpen(1);
+ sub_run_cycles(target - SekTotalCycles());
+ SekClose();
+ SekOpen(0);
+}
+
+void SegaCDNewFrame()
+{
+ SekNewFrame();
+
+ SekOpen(1);
+ SekIdle(sub_carry);
+ sub_cycles_frame = SekTotalCycles();
+ SekClose();
+}
+
+void SegaCDEndFrame(INT32 main_cycles)
+{
+ INT32 target = (INT64)main_cycles * SCD_CLOCK / main_clock;
+ sub_carry = sub_cycles_frame - target;
+ if (sub_carry < 0) sub_carry = 0;
+
+ // cd-da fader, ramps one step per sample
+ INT32 step = 44100 / ((nBurnFPS > 0) ? (nBurnFPS / 100) : 60);
+ if (cdd.dac_attenuated < cdd.dac_attenuator) {
+ cdd.dac_attenuated = ((cdd.dac_attenuator - cdd.dac_attenuated) > step) ? cdd.dac_attenuated + step : cdd.dac_attenuator;
+ } else if (cdd.dac_attenuated > cdd.dac_attenuator) {
+ cdd.dac_attenuated = ((cdd.dac_attenuated - cdd.dac_attenuator) > step) ? cdd.dac_attenuated - step : cdd.dac_attenuator;
+ }
+}
+
+void SegaCDSoundUpdate(INT16 *output, INT32 samples)
+{
+ if (output == NULL) {
+ pcm_fifo_read = pcm_fifo_count = 0;
+ return;
+ }
+
+ // keep the pcm latency bounded
+ INT32 needed = pcm_stream.samples_to_source(samples);
+ while (pcm_fifo_count > needed * 2) {
+ pcm_fifo_read = (pcm_fifo_read + 1) % PCM_FIFO_SIZE;
+ pcm_fifo_count--;
+ }
+
+ pcm_stream.render(output, samples);
+
+ CDEmuSetVolume(cdd.dac_attenuated * 50.0 / 0x4000);
+ CDEmuGetSoundBuffer(output, samples);
+}
+
+void SegaCDSetPAL(INT32 pal)
+{
+ main_clock = pal ? MAIN_PAL : MAIN_NTSC;
+}
+
+// -------------------------------------------------------------------------
+// bios / region
+
+static INT32 scd_detect_region()
+{
+ UINT8 sector[2048];
+
+ if (CDEmuReadDataSector(0, sector)) return 0;
+ if (memcmp(sector, "SEGADISCSYSTEM", 14) && memcmp(sector, "SEGABOOTDISC", 12)) return 0;
+
+ // the bios checks the region specific security code, header region field is unreliable (Sol-Feace EU says 'U')
+ switch (sector[0x20b]) {
+ case 0x64: return 1;
+ case 0xa1: return 2;
+ }
+
+ return 0;
+}
+
+INT32 SegaCDLoadBios(UINT8 *dest)
+{
+ INT32 select = MegadriveDIP[0] & 0x03;
+ INT32 wanted = (select) ? (select - 1) : scd_detect_region();
+ INT32 loaded = -1;
+
+ for (INT32 n = 0; n < 4 && loaded < 0; n++) {
+ INT32 idx = (n == 0) ? wanted : (n - 1);
+ if (n && idx == wanted) continue;
+
+ struct BurnRomInfo ri;
+ ri.nLen = 0;
+ BurnDrvGetRomInfo(&ri, idx);
+ if (ri.nLen != 0x20000) continue;
+
+ if (BurnLoadRom(dest, idx, 1) == 0) loaded = idx;
+ }
+
+ if (loaded < 0) {
+ bprintf(PRINT_ERROR, _T("Sega CD: no bios could be loaded\n"));
+ return 1;
+ }
+
+ if (loaded != wanted) {
+ bprintf(PRINT_IMPORTANT, _T("Sega CD: wanted bios #%d is unavailable, using bios #%d\n"), wanted, loaded);
+ }
+
+ // accept both big-endian and byteswapped dumps
+ if (!(dest[0x100] == 'E' && dest[0x101] == 'S')) {
+ BurnByteswap(dest, 0x20000);
+ }
+
+ scd_bios_index = loaded;
+
+ return 0;
+}
+
+UINT8 SegaCDGetHardware()
+{
+ // version register: region bits, bit 5 clear = expansion unit attached
+ static const UINT8 hw[3] = { 0x80, 0xc0, 0x00 };
+
+ return hw[scd_bios_index % 3];
+}
+
+// -------------------------------------------------------------------------
+// init / reset / exit
+
+static void scd_power(INT32 reset)
+{
+ sub_run = 0;
+ sub_request = 0;
+ sub_halt = 1;
+ sub_reset_pending = 1;
+ sub_wram_wait = 0;
+
+ wram_mode = 0;
+ wram_switch_request = 0;
+ wram_switch = 0;
+ wram_select = 0;
+ wram_priority = 0;
+ pram_bank = 0;
+ pram_protect = 0;
+ vector_level4 = reset ? vector_level4 : 0xffffffff;
+ led_status = 0;
+
+ comm_cfm = comm_cfs = 0;
+ memset(comm_command, 0, sizeof(comm_command));
+ memset(comm_status, 0, sizeof(comm_status));
+
+ counter_divider = 0;
+ counter_dma = 0;
+
+ irq_mask = reset ? (irq_mask & 0x40) : 0;
+ irq_pending = reset ? (irq_pending & 0x40) : 0;
+ irq_dirty = 1;
+
+ timer_frequency = 0;
+ timer_counter = 0;
+
+ memset(&gpu, 0, sizeof(gpu));
+
+ cdc_power();
+ pcm_power();
+ cdd_power();
+
+}
+
+static void bram_format()
+{
+ // blank internal backup ram directory, so the bios doesn't ask to format it
+ static const UINT8 format[0x40] = {
+ 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x7d, 0x00, 0x7d, 0x00, 0x7d, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x53, 0x45, 0x47, 0x41, 0x5f, 0x43, 0x44, 0x5f, 0x52, 0x4f, 0x4d, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x52, 0x41, 0x4d, 0x5f, 0x43, 0x41, 0x52, 0x54, 0x52, 0x49, 0x44, 0x47, 0x45, 0x5f, 0x5f, 0x5f
+ };
+
+ memset(BackupRam, 0, 0x2000);
+ memcpy(BackupRam + 0x2000 - 0x40, format, 0x40);
+}
+
+INT32 SegaCDInit(UINT8 *bios)
+{
+ BiosRom = bios;
+
+ INT32 nLen = 0x80000 + 0x40000 + 0x4000 + 0x10000 + 0x2000;
+ AllMemScd = (UINT8*)BurnMalloc(nLen);
+ if (AllMemScd == NULL) return 1;
+ memset(AllMemScd, 0, nLen);
+
+ UINT8 *Next = AllMemScd;
+ PrgRam = Next; Next += 0x80000;
+ WordRam = (UINT16*)Next; Next += 0x40000;
+ CdcRam = (UINT16*)Next; Next += 0x4000;
+ PcmRam = Next; Next += 0x10000;
+ BackupRam = Next; Next += 0x2000;
+
+ bram_format();
+ toc_build();
+
+ if (!disc_present) {
+ bprintf(PRINT_IMPORTANT, _T("Sega CD: no disc\n"));
+ }
+
+ // main cpu: bios, prg-ram window and word ram in the 0x000000-0x3fffff expansion area
+ SekOpen(0);
+ SekMapHandler(5, 0x000000, 0x3fffff, MAP_RAM);
+ for (INT32 i = 0; i < 0x200000; i += 0x40000) {
+ SekMapMemory(BiosRom, i, i + 0x1ffff, MAP_ROM);
+ }
+ SekMapHandler(5, 0x000000, 0x0003ff, MAP_READ); // level 4 vector redirection at 0x70
+ SekMapHandler(6, 0xa12000, 0xa123ff, MAP_RAM);
+ SekMapHandler(7, 0x400000, 0x7fffff, MAP_WRITE);
+
+ SekSetReadByteHandler (5, main_read_byte);
+ SekSetReadWordHandler (5, main_read_word);
+ SekSetWriteByteHandler(5, main_write_byte);
+ SekSetWriteWordHandler(5, main_write_word);
+
+ SekSetReadByteHandler (6, main_io_read_byte);
+ SekSetReadWordHandler (6, main_io_read_word);
+ SekSetWriteByteHandler(6, main_io_write_byte);
+ SekSetWriteWordHandler(6, main_io_write_word);
+
+ SekSetWriteByteHandler(7, main_cart_write_byte);
+ SekSetWriteWordHandler(7, main_cart_write_word);
+ SekClose();
+
+ // sub cpu
+ SekInit(1, 0x68000);
+ SekOpen(1);
+ SekMapMemory(PrgRam, 0x000000, 0x07ffff, MAP_ROM); // writes go through the handler (write protection)
+ SekSetReadByteHandler (0, sub_read_byte);
+ SekSetReadWordHandler (0, sub_read_word);
+ SekSetWriteByteHandler(0, sub_write_byte);
+ SekSetWriteWordHandler(0, sub_write_word);
+ SekSetIrqCallback(sub_irq_callback);
+ SekClose();
+
+ pcm_stream.init(SCD_PCM_RATE, nBurnSoundRate, 2, 1, pcm_stream_update);
+ pcm_stream.set_volume(1.00);
+
+ main_clock = MAIN_NTSC;
+ sub_carry = 0;
+ sub_cycles_frame = 0;
+
+ scd_power(0);
+
+ return 0;
+}
+
+void SegaCDReset()
+{
+ scd_power(1);
+
+ SekOpen(1);
+ SekReset();
+ sub_irq_update();
+ SekClose();
+
+ sub_carry = 0;
+ sub_cycles_frame = 0;
+}
+
+void SegaCDExit()
+{
+ if (cdd.audio_playing) CDEmuStop();
+
+ pcm_stream.exit();
+
+ BurnFree(AllMemScd);
+ AllMemScd = NULL;
+
+ disc_present = 0;
+ scd_bios_index = 0;
+ SegaCDMode = 0;
+}
+
+INT32 SegaCDScan(INT32 nAction, INT32 *pnMin)
+{
+ if (nAction & ACB_VOLATILE) {
+ ScanVar(PrgRam, 0x80000, "SCD PRG RAM");
+ ScanVar(WordRam, 0x40000, "SCD Word RAM");
+ ScanVar(CdcRam, 0x4000, "SCD CDC RAM");
+ ScanVar(PcmRam, 0x10000, "SCD PCM RAM");
+
+ SCAN_VAR(sub_run);
+ SCAN_VAR(sub_request);
+ SCAN_VAR(sub_halt);
+ SCAN_VAR(sub_reset_pending);
+ SCAN_VAR(sub_wram_wait);
+ SCAN_VAR(sub_carry);
+ SCAN_VAR(sub_cycles_frame);
+ SCAN_VAR(wram_mode);
+ SCAN_VAR(wram_switch_request);
+ SCAN_VAR(wram_switch);
+ SCAN_VAR(wram_select);
+ SCAN_VAR(wram_priority);
+ SCAN_VAR(pram_bank);
+ SCAN_VAR(pram_protect);
+ SCAN_VAR(vector_level4);
+ SCAN_VAR(led_status);
+ SCAN_VAR(comm_cfm);
+ SCAN_VAR(comm_cfs);
+ SCAN_VAR(comm_command);
+ SCAN_VAR(comm_status);
+ SCAN_VAR(counter_divider);
+ SCAN_VAR(counter_dma);
+ SCAN_VAR(irq_mask);
+ SCAN_VAR(irq_pending);
+ SCAN_VAR(timer_frequency);
+ SCAN_VAR(timer_counter);
+ SCAN_VAR(cdc);
+ SCAN_VAR(cdd);
+ SCAN_VAR(gpu);
+ SCAN_VAR(pcm_ch);
+ SCAN_VAR(pcm_enable);
+ SCAN_VAR(pcm_bank);
+ SCAN_VAR(pcm_channel);
+
+ CDEmuScan(nAction, pnMin);
+
+ if (nAction & ACB_WRITE) {
+ pcm_fifo_read = pcm_fifo_count = 0;
+ irq_dirty = 1;
+ }
+ }
+
+ if (nAction & ACB_NVRAM) {
+ struct BurnArea ba;
+ memset(&ba, 0, sizeof(ba));
+ ba.Data = BackupRam;
+ ba.nLen = 0x2000;
+ ba.nAddress = 0;
+ ba.szName = "SCD Backup RAM";
+ BurnAcb(&ba);
+ }
+
+ return 0;
+}
diff --git a/src/burn/drv/megadrive/segacd.h b/src/burn/drv/megadrive/segacd.h
new file mode 100644
index 000000000..b8db53de0
--- /dev/null
+++ b/src/burn/drv/megadrive/segacd.h
@@ -0,0 +1,27 @@
+#pragma once
+#ifndef SEGACD_H
+#define SEGACD_H
+
+extern INT32 SegaCDMode;
+extern INT32 (*SegaCDMainCycles)(); // main cpu cycles done in the current frame
+
+// bios / region
+INT32 SegaCDLoadBios(UINT8 *dest);
+UINT8 SegaCDGetHardware();
+
+// main cpu (cpu #0) must be initialised and closed when calling these
+INT32 SegaCDInit(UINT8 *bios);
+void SegaCDExit();
+void SegaCDReset();
+void SegaCDSetPAL(INT32 pal);
+
+// frame handling, called with cpu #0 closed
+void SegaCDNewFrame();
+// called with cpu #0 open
+void SegaCDRunSub(INT32 main_cycles);
+void SegaCDEndFrame(INT32 main_cycles);
+void SegaCDSoundUpdate(INT16 *output, INT32 samples);
+
+INT32 SegaCDScan(INT32 nAction, INT32 *pnMin);
+
+#endif
diff --git a/src/burner/cdlist.cpp b/src/burner/cdlist.cpp
index 6361eef84..80b62869b 100644
--- a/src/burner/cdlist.cpp
+++ b/src/burner/cdlist.cpp
@@ -2,9 +2,11 @@
#include "cdlist.h"
#include "neocdlist.h"
#include "pcecdlist.h"
+#include "segacdlist.h"
#include "cd_img.h"
#include "neocdlist_games.h"
#include "pcecdlist_games.h"
+#include "segacdlist_games.h"
static NGCDGAME* pNeoGame;
@@ -268,6 +270,25 @@ static INT32 IsGamesExpress(CDImage* pImage, const CDImageTrack* pTrack, UINT8*
return nCrc == 0xd7b47c06 || nCrc == 0x86aec522 || nCrc == 0xc8d1b5ef || nCrc == 0x0bdbde64;
}
+// Sega CD / Mega-CD disc header: system id at 0x000, titles at 0x120 (domestic) / 0x150 (overseas), region at 0x1f0
+static INT32 IsSegaCDHeader(const UINT8* pSector, INT32 nSize)
+{
+ return nSize >= 0x200 && (!memcmp(pSector, "SEGADISCSYSTEM", 14) || !memcmp(pSector, "SEGABOOTDISC", 12));
+}
+
+static void SegaCDHeaderText(TCHAR* pszDest, UINT32 nDest, const UINT8* pText, INT32 nLength)
+{
+ UINT32 n = 0;
+ for (INT32 i = 0; i < nLength && n + 1 < nDest; i++) {
+ UINT8 c = pText[i];
+ if (c < 0x20 || c > 0x7e) c = ' ';
+ if (c == ' ' && (n == 0 || pszDest[n - 1] == _T(' '))) continue;
+ pszDest[n++] = (TCHAR)c;
+ }
+ while (n && pszDest[n - 1] == _T(' ')) n--;
+ pszDest[n] = 0;
+}
+
static INT32 IdentifyPlatform(CDImage* pImage, CDListResult* pResult)
{
static const UINT8 pPceMagic[32] = {
@@ -293,6 +314,18 @@ static INT32 IdentifyPlatform(CDImage* pImage, CDListResult* pResult)
return 0;
INT32 nSize = 0;
+ if (ReadSector(pImage, pTrack->nIndex1LBA, sector, &nSize) && IsSegaCDHeader(sector, nSize)) {
+ pResult->nPlatform = CDLIST_PLATFORM_SEGACD;
+ pResult->nSource = CDLIST_SOURCE_MAGIC;
+ pResult->nConfidence = CDLIST_CONFIDENCE_EXACT;
+ SegaCDHeaderText(pResult->Metadata.szTitle, CDLIST_TEXT_SIZE, sector + 0x150, 0x30);
+ if (!pResult->Metadata.szTitle[0])
+ SegaCDHeaderText(pResult->Metadata.szTitle, CDLIST_TEXT_SIZE, sector + 0x120, 0x30);
+ SegaCDHeaderText(pResult->Metadata.szRegion, 64, sector + 0x1f0, 0x10);
+ CopyText(pResult->Metadata.szRequirement, CDLIST_TEXT_SIZE, _T("Sega CD / Mega-CD BIOS"));
+ CopyText(pResult->Metadata.szDescriptor, CDLIST_TEXT_SIZE, _T("Sega CD disc system header"));
+ return 1;
+ }
if (ReadSector(pImage, pTrack->nIndex1LBA, sector, &nSize) && nSize >= 32 && !memcmp(sector, pPceMagic, sizeof(pPceMagic))) {
pResult->nPlatform = CDLIST_PLATFORM_PCECD;
pResult->nSource = CDLIST_SOURCE_MAGIC;
@@ -320,6 +353,18 @@ static INT32 IdentifyPlatform(CDImage* pImage, CDListResult* pResult)
return 0;
}
+static const sega_cd_game* FindSegaCDGame(const TCHAR* pszSha1)
+{
+ if (!pszSha1 || !pszSha1[0])
+ return NULL;
+
+ for (UINT32 i = 0; i < sizeof(segacd_games) / sizeof(segacd_games[0]); i++) {
+ if (segacd_games[i].sha1[0] && !_tcscmp(segacd_games[i].sha1, pszSha1))
+ return &segacd_games[i];
+ }
+ return NULL;
+}
+
static void FillNeoMetadata(CDListResult* pResult)
{
NGCDGAME* pGame = GetNeoGeoCDInfo(pResult->nNeoID);
@@ -391,6 +436,17 @@ INT32 CDListIdentifyEx(const TCHAR* pszPath, CDListResult* pResult, UINT32 nFlag
break;
}
}
+
+ if (pResult->nPlatform == CDLIST_PLATFORM_SEGACD) {
+ const sega_cd_game* pGame = FindSegaCDGame(pResult->szTOCSha1);
+ if (pGame) {
+ CopyText(pResult->Metadata.szTitle, CDLIST_TEXT_SIZE, pGame->name);
+ CopyText(pResult->Metadata.szName, CDLIST_TEXT_SIZE, pGame->id);
+ CopyText(pResult->Metadata.szYear, 32, pGame->year);
+ CopyText(pResult->Metadata.szCompany, CDLIST_TEXT_SIZE, pGame->company);
+ pResult->nSource = CDLIST_SOURCE_GAMEDB;
+ }
+ }
}
CDImageClose(pImage);
return pResult->nPlatform != CDLIST_PLATFORM_UNKNOWN;
@@ -598,6 +654,98 @@ void PceCDInfo_Exit()
}
#endif
+#ifdef BUILD_MEGADRIVE
+static CDListMetadata SegaCdInfo;
+static INT32 nSegaCdInfoValid = 0;
+
+static INT32 IsSegaCD()
+{
+ return (BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGACD;
+}
+
+INT32 SegaCDInfo_Init()
+{
+ SegaCDInfo_Exit();
+
+ if (!IsSegaCD() || !CDEmuImage[0]) {
+ return 0;
+ }
+
+ // the short name comes from the image file name, so that each image gets its own saves
+ TCHAR szFile[CDLIST_TEXT_SIZE];
+ const TCHAR* pszBase = _tcsrchr(CDEmuImage, _T('\\'));
+ pszBase = pszBase ? pszBase + 1 : CDEmuImage;
+ const TCHAR* pszSlash = _tcsrchr(pszBase, _T('/'));
+ if (pszSlash) {
+ pszBase = pszSlash + 1;
+ }
+ CopyText(szFile, CDLIST_TEXT_SIZE, pszBase);
+ TCHAR* pszDot = _tcsrchr(szFile, _T('.'));
+ if (pszDot) {
+ *pszDot = _T('\0');
+ }
+
+ INT32 nLen = 0;
+ for (INT32 i = 0; szFile[i] && nLen < 32; i++) {
+ TCHAR c = _totlower(szFile[i]);
+ if (c == _T('-') || c == _T('(') || c == _T('[')) break;
+ if ((c >= _T('a') && c <= _T('z')) || (c >= _T('0') && c <= _T('9'))) {
+ SegaCdInfo.szName[nLen++] = c;
+ }
+ }
+ SegaCdInfo.szName[nLen] = _T('\0');
+ if (nLen == 0) {
+ _tcscpy(SegaCdInfo.szName, _T("unknown"));
+ }
+
+ const sega_cd_game* pGame = FindSegaCDGame(ANSIToTCHAR((char*)CDEmuImageTOCSHA1, NULL, 0));
+ if (pGame) {
+ bprintf(0, _T("segacdlist_games db: found entry %s\n"), pGame->id);
+ CopyText(SegaCdInfo.szTitle, CDLIST_TEXT_SIZE, pGame->name);
+ CopyText(SegaCdInfo.szName, CDLIST_TEXT_SIZE, pGame->id);
+ CopyText(SegaCdInfo.szYear, 32, pGame->year);
+ CopyText(SegaCdInfo.szCompany, CDLIST_TEXT_SIZE, pGame->company);
+ } else {
+ CDListResult Result;
+ if (CDListIdentifyEx(CDEmuImage, &Result, CDLIST_IDENTIFY_FAST, NULL, NULL, NULL) && Result.nPlatform == CDLIST_PLATFORM_SEGACD && Result.Metadata.szTitle[0]) {
+ CopyText(SegaCdInfo.szTitle, CDLIST_TEXT_SIZE, Result.Metadata.szTitle);
+ } else {
+ CopyText(SegaCdInfo.szTitle, CDLIST_TEXT_SIZE, szFile);
+ }
+ }
+
+ nSegaCdInfoValid = 1;
+
+ bprintf(PRINT_NORMAL, _T(" Title: %s\n" ), SegaCdInfo.szTitle);
+ bprintf(PRINT_NORMAL, _T(" Shortname: %s\n"), SegaCdInfo.szName);
+ if (SegaCdInfo.szYear[0]) bprintf(PRINT_NORMAL, _T(" Year: %s\n" ), SegaCdInfo.szYear);
+ if (SegaCdInfo.szCompany[0]) bprintf(PRINT_NORMAL, _T(" Company: %s\n"), SegaCdInfo.szCompany);
+
+ return nSegaCdInfoValid;
+}
+
+TCHAR* SegaCDInfo_Text(INT32 nText)
+{
+ if (!IsSegaCD() || !nSegaCdInfoValid) {
+ return NULL;
+ }
+
+ switch (nText) {
+ case DRV_FULLNAME: return SegaCdInfo.szTitle;
+ case DRV_NAME: return SegaCdInfo.szName;
+ case DRV_MANUFACTURER: return SegaCdInfo.szCompany[0] ? SegaCdInfo.szCompany : NULL;
+ case DRV_DATE: return SegaCdInfo.szYear[0] ? SegaCdInfo.szYear : NULL;
+ }
+ return NULL;
+}
+
+void SegaCDInfo_Exit()
+{
+ memset(&SegaCdInfo, 0, sizeof(SegaCdInfo));
+ nSegaCdInfoValid = 0;
+}
+#endif
+
TCHAR* CDInfo_Text(INT32 nText)
{
static TCHAR szNone[] = _T("NOGAME");
@@ -608,6 +756,8 @@ TCHAR* CDInfo_Text(INT32 nText)
return NeoCDInfo_Text(nText);
case CDGAME_PCE:
return PceCDInfo_Text(nText);
+ case CDGAME_SEGACD:
+ return SegaCDInfo_Text(nText);
default:
return szNone;
}
@@ -617,7 +767,7 @@ TCHAR *CDInfo_GamePrefix()
{
static TCHAR buffer[128];
- TCHAR g_sys_prefixes[3][20] = { _T("none_"), _T("ngcd_"), _T("pcecd_") };
+ TCHAR g_sys_prefixes[4][20] = { _T("none_"), _T("ngcd_"), _T("pcecd_"), _T("segacd_") };
const INT32 g_type = CDGameType();
_stprintf(buffer, _T("%s%s"), g_sys_prefixes[g_type], CDInfo_Text(DRV_NAME));
diff --git a/src/burner/cdlist.h b/src/burner/cdlist.h
index bdadb99c8..af1384ec1 100644
--- a/src/burner/cdlist.h
+++ b/src/burner/cdlist.h
@@ -11,7 +11,8 @@
enum {
CDGAME_NONE = 0, // not a CD game
CDGAME_NEOGEO, // Neo Geo CD
- CDGAME_PCE // PC Engine CD
+ CDGAME_PCE, // PC Engine CD
+ CDGAME_SEGACD // Sega CD / Mega-CD
};
static inline INT32 CDGameType()
@@ -23,6 +24,9 @@ static inline INT32 CDGameType()
if (nHardware == HARDWARE_PCENGINE_PCE_CD) {
return CDGAME_PCE;
}
+ if (nHardware == HARDWARE_SEGA_MEGACD) {
+ return CDGAME_SEGACD;
+ }
return CDGAME_NONE;
}
@@ -38,7 +42,8 @@ enum CDListPlatform {
CDLIST_PLATFORM_UNKNOWN = 0,
CDLIST_PLATFORM_NEOCD,
CDLIST_PLATFORM_PCECD,
- CDLIST_PLATFORM_PCFX
+ CDLIST_PLATFORM_PCFX,
+ CDLIST_PLATFORM_SEGACD
};
enum CDListSource {
diff --git a/src/burner/dat.cpp b/src/burner/dat.cpp
index 5878dfcee..6a0420aaf 100644
--- a/src/burner/dat.cpp
+++ b/src/burner/dat.cpp
@@ -177,6 +177,7 @@ INT32 write_datfile(INT32 bType, FILE* fDat)
}
if ((((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGADRIVE)
+ || ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGACD)
|| ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_PCENGINE_PCENGINE)
|| ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_PCENGINE_PCE_CD)
|| ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_PCENGINE_TG16)
@@ -198,7 +199,7 @@ INT32 write_datfile(INT32 bType, FILE* fDat)
continue;
}
- if (((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) != HARDWARE_SEGA_MEGADRIVE) && (bType == DAT_MEGADRIVE_ONLY)) {
+ if (((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) != HARDWARE_SEGA_MEGADRIVE) && ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) != HARDWARE_SEGA_MEGACD) && (bType == DAT_MEGADRIVE_ONLY)) {
continue;
}
@@ -345,6 +346,7 @@ INT32 write_datfile(INT32 bType, FILE* fDat)
}
remove_driver_leader(HARDWARE_SEGA_MEGADRIVE, 3, 1)
+ remove_driver_leader(HARDWARE_SEGA_MEGACD, 3, 1)
remove_driver_leader(HARDWARE_PCENGINE_TG16, 3, 1)
remove_driver_leader(HARDWARE_COLECO, 3, 1)
remove_driver_leader(HARDWARE_SEGA_SG1000, 5, 1)
@@ -696,6 +698,7 @@ INT32 write_datfile(INT32 bType, FILE* fDat)
}
if ((((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGADRIVE)
+ || ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGACD)
|| ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_PCENGINE_PCENGINE)
|| ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_PCENGINE_PCE_CD)
|| ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_PCENGINE_TG16)
@@ -717,7 +720,7 @@ INT32 write_datfile(INT32 bType, FILE* fDat)
continue;
}
- if (((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) != HARDWARE_SEGA_MEGADRIVE) && (bType == DAT_MEGADRIVE_ONLY)) {
+ if (((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) != HARDWARE_SEGA_MEGADRIVE) && ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) != HARDWARE_SEGA_MEGACD) && (bType == DAT_MEGADRIVE_ONLY)) {
continue;
}
@@ -797,6 +800,7 @@ INT32 write_datfile(INT32 bType, FILE* fDat)
#endif
remove_driver_leader(HARDWARE_SEGA_MEGADRIVE, 3, 0)
+ remove_driver_leader(HARDWARE_SEGA_MEGACD, 3, 0)
remove_driver_leader(HARDWARE_PCENGINE_TG16, 3, 0)
remove_driver_leader(HARDWARE_COLECO, 3, 0)
remove_driver_leader(HARDWARE_SEGA_SG1000, 5, 0)
diff --git a/src/burner/gami.cpp b/src/burner/gami.cpp
index 65e9770ad..c29bd5411 100644
--- a/src/burner/gami.cpp
+++ b/src/burner/gami.cpp
@@ -59,7 +59,8 @@ UINT8 *lua_hotkeys[] = {
#define HW_NEOGEO ( ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SNK_NEOGEO) || ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SNK_NEOCD) )
#define HW_NES ( ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_NES) || ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_FDS) )
-#define HW_MISC ( (! HW_NEOGEO) && (! HW_NES) && ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) != HARDWARE_SEGA_MEGADRIVE) )
+#define HW_MEGADRIVE ( ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGADRIVE) || ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGACD) )
+#define HW_MISC ( (! HW_NEOGEO) && (! HW_NES) && (! HW_MEGADRIVE) )
#define SETS_VS ( (BurnDrvGetGenreFlags() & GBF_VSFIGHT) && ((BurnDrvGetFamilyFlags() & FBF_SF) || (BurnDrvGetFamilyFlags() & FBF_DSTLK) || (BurnDrvGetFamilyFlags() &FBF_PWRINST) || HW_NEOGEO) )
#ifdef BUILD_MACOS
@@ -1102,7 +1103,7 @@ static void GameInpInitMacros()
nMacroCount++;
pgi++;
}
- if ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGADRIVE) {
+ if (HW_MEGADRIVE) {
pgi->nInput = GIT_MACRO_AUTO;
pgi->nType = BIT_DIGITAL;
pgi->Macro.nMode = 0;
@@ -2179,6 +2180,7 @@ tIniStruct gamehw_cfg[] = {
{_T("MegaDrive hardware"), _T("config/presets/megadrive.ini"), true, { HARDWARE_SEGA_MEGADRIVE, 0 }, "\t\t\t- nPlatform == CDLIST_PLATFORM_PCECD) ? _T("pcecd_") : _T("ngcd_"), pszName);
+ _stprintf(tszName, _T("%s%s"), (pEntry->nPlatform == CDLIST_PLATFORM_PCECD) ? _T("pcecd_") : ((pEntry->nPlatform == CDLIST_PLATFORM_SEGACD) ? _T("segacd_") : _T("ngcd_")), pszName);
if (!nSide) {
BuildPreviewPath(szPath, MAX_PATH, szNeoCDCoverDir, tszName);
@@ -308,6 +308,8 @@ static const TCHAR* PlatformText(INT32 nPlatform)
return _T("Neo Geo CD");
if (nPlatform == CDLIST_PLATFORM_PCECD)
return _T("PCE CD");
+ if (nPlatform == CDLIST_PLATFORM_SEGACD)
+ return _T("Sega CD");
return _T("Unknown");
}
@@ -606,6 +608,20 @@ static void ShowZoom(INT32 nSide)
nZoomPngSize = 0;
}
+static INT32 PlatformAvailable(INT32 nPlatform)
+{
+#ifdef BUILD_NEOGEO
+ if (nPlatform == CDLIST_PLATFORM_NEOCD) return 1;
+#endif
+#ifdef BUILD_PCE
+ if (nPlatform == CDLIST_PLATFORM_PCECD) return 1;
+#endif
+#ifdef BUILD_MEGADRIVE
+ if (nPlatform == CDLIST_PLATFORM_SEGACD) return 1;
+#endif
+ return 0;
+}
+
static bool SelectForPlay()
{
const CDLibraryEntry* pEntry = GetLibraryEntry(nSelectedItem);
@@ -613,13 +629,7 @@ static bool SelectForPlay()
MessageBox(hNeoCDWnd, _T("Select a CD image first."), _T(APP_TITLE), MB_OK | MB_ICONWARNING);
return false;
}
-#if !defined(BUILD_NEOGEO)
- if (pEntry->nPlatform != CDLIST_PLATFORM_PCECD) {
-#elif !defined(BUILD_PCE)
- if (pEntry->nPlatform != CDLIST_PLATFORM_NEOCD) {
-#else
- if (pEntry->nPlatform != CDLIST_PLATFORM_NEOCD && pEntry->nPlatform != CDLIST_PLATFORM_PCECD) {
-#endif
+ if (!PlatformAvailable(pEntry->nPlatform)) {
MessageBox(hNeoCDWnd, _T("The required CD driver is unavailable in this build."), _T(APP_TITLE), MB_OK | MB_ICONERROR);
return false;
}
@@ -629,6 +639,12 @@ static bool SelectForPlay()
return false;
}
#endif
+#ifdef BUILD_MEGADRIVE
+ if (pEntry->nPlatform == CDLIST_PLATFORM_SEGACD && BurnDrvGetIndex((char*)"md_segacd") < 0) {
+ MessageBox(hNeoCDWnd, _T("The Sega CD driver is unavailable."), _T(APP_TITLE), MB_OK | MB_ICONERROR);
+ return false;
+ }
+#endif
#ifdef BUILD_PCE
if (pEntry->nPlatform == CDLIST_PLATFORM_PCECD && BurnDrvGetIndex((char*)"pce_scdsys") < 0) {
MessageBox(hNeoCDWnd, _T("The PCE CD driver is unavailable."), _T(APP_TITLE), MB_OK | MB_ICONERROR);
@@ -709,6 +725,9 @@ static INT_PTR CALLBACK NeoCDList_WndProc(HWND hDlg, UINT nMessage, WPARAM wPara
#endif
#ifdef BUILD_PCE
if (nPlatform == CDLIST_PLATFORM_PCECD) BurnerLoadDriver(_T("pce_scdsys"));
+#endif
+#ifdef BUILD_MEGADRIVE
+ if (nPlatform == CDLIST_PLATFORM_SEGACD) BurnerLoadDriver(_T("md_segacd"));
#endif
return TRUE;
}
diff --git a/src/burner/win32/scrn.cpp b/src/burner/win32/scrn.cpp
index 3c233f3b5..d5f34acd4 100644
--- a/src/burner/win32/scrn.cpp
+++ b/src/burner/win32/scrn.cpp
@@ -1,6 +1,7 @@
// Screen Window
#include "burner.h"
#include "pcecdlist.h"
+#include "segacdlist.h"
#include
#include
@@ -22,6 +23,17 @@ static void SetPCECDTitle()
}
#endif
+#ifdef BUILD_MEGADRIVE
+static void SetSegaCDTitle()
+{
+ TCHAR szText[1024] = _T("");
+ TCHAR* pszTitle = SegaCDInfo_Text(DRV_FULLNAME);
+ _sntprintf(szText, _countof(szText), _T(APP_TITLE) _T(" v%.20s") _T(SEPERATOR_1) _T("%s") _T(SEPERATOR_1) _T("%s"), szAppBurnVer, BurnDrvGetText(DRV_FULLNAME), pszTitle ? pszTitle : FBALoadStringEx(hAppInst, IDS_UNIDENTIFIED_CD, true));
+ szText[_countof(szText) - 1] = _T('\0');
+ SetWindowText(hScrnWnd, szText);
+}
+#endif
+
#ifdef BUILD_NEOGEO
static void SetNeoCDTitle(TCHAR* pszTitle)
{
@@ -1100,6 +1112,7 @@ static t_hw_Struct scrn_gamehw_cfg[] = {
{ "neogeo", { HARDWARE_SNK_NEOGEO, HARDWARE_SNK_MVS, HARDWARE_SNK_DEDICATED_PCB, 0 } },
{ "neogeocd", { HARDWARE_SNK_NEOCD, 0 } },
{ "pcecd", { HARDWARE_PCENGINE_PCE_CD, 0 } },
+ { "segacd", { HARDWARE_SEGA_MEGACD, 0 } },
{ "arcade", { ~0, 0 } }, // default, if not found above
{ "\0", { 0 } } // end
};
@@ -1608,6 +1621,9 @@ INT32 BurnerQuickLoad(const INT32 nMode, const TCHAR* pszSelect)
case CDLIST_PLATFORM_PCECD:
pszDrvName = _T("pce_scdsys");
break;
+ case CDLIST_PLATFORM_SEGACD:
+ pszDrvName = _T("md_segacd");
+ break;
default:
FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_CD_IMAGE_IDENTIFY), pszSelect);
FBAPopupDisplay(PUF_TYPE_ERROR);
@@ -4727,6 +4743,12 @@ int ScrnTitle()
return 0;
}
#endif
+#ifdef BUILD_MEGADRIVE
+ if ((BurnDrvGetHardwareCode() & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGACD) {
+ SetSegaCDTitle();
+ return 0;
+ }
+#endif
} else {
_stprintf(szText, _T(APP_TITLE) _T( " v%.20s") _T(SEPERATOR_1) _T("[%s]"), szAppBurnVer, FBALoadStringEx(hAppInst, IDS_SCRN_NOGAME, true));
diff --git a/src/burner/win32/sel.cpp b/src/burner/win32/sel.cpp
index 75003125d..79a008eca 100644
--- a/src/burner/win32/sel.cpp
+++ b/src/burner/win32/sel.cpp
@@ -2056,7 +2056,7 @@ void LoadDrvIcons()
// By hardwares
if (bIconsByHardwares) {
- if ((nCode & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGADRIVE) {
+ if ((nCode & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGADRIVE || (nCode & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGACD) {
hDrvIcon[nDrvIndex] = pIconsCache[nDrvCount + ICON_MEGADRIVE]; continue;
}
else
diff --git a/src/intf/cd/cd_img.inc b/src/intf/cd/cd_img.inc
index cfedb7bbe..0014b0f67 100644
--- a/src/intf/cd/cd_img.inc
+++ b/src/intf/cd/cd_img.inc
@@ -1756,6 +1756,18 @@ static UINT8* cdimgReadTOC(INT32 track)
return TOCEntry;
}
+ if (track & CDEmuTOC_TRACKEND) {
+ track = bcd(track & 0xff);
+ if (track >= cdimgTOC->FirstTrack && track <= cdimgTOC->LastTrack) {
+ TOCEntry[0] = cdimgTOC->TrackData[track - 1].EndAddress[1];
+ TOCEntry[1] = cdimgTOC->TrackData[track - 1].EndAddress[2];
+ TOCEntry[2] = cdimgTOC->TrackData[track - 1].EndAddress[3];
+ TOCEntry[3] = cdimgTOC->TrackData[track - 1].Control >> 4;
+ }
+
+ return TOCEntry;
+ }
+
track = bcd(track);
if (track >= cdimgTOC->FirstTrack && track <= cdimgTOC->LastTrack) {
TOCEntry[0] = cdimgTOC->TrackData[track - 1].Address[1];
diff --git a/src/intf/cd/cd_interface.h b/src/intf/cd/cd_interface.h
index a43237a4f..c505603e4 100644
--- a/src/intf/cd/cd_interface.h
+++ b/src/intf/cd/cd_interface.h
@@ -5,7 +5,8 @@
// CD emulation module
enum CDEmuStatusValue { idle = 0, reading, playing, paused, seeking, fastforward, fastreverse };
-enum CDEmuReadTOCFlags { CDEmuTOC_FIRSTLAST = 0x1000, CDEmuTOC_LASTMSF, CDEmuTOC_FIRSTINDEX, CDEmuTOC_ENDOFDISC };
+// CDEmuTOC_TRACKEND is or'ed with a bcd track number: msf where the track ends (start of the next track's pregap)
+enum CDEmuReadTOCFlags { CDEmuTOC_FIRSTLAST = 0x1000, CDEmuTOC_LASTMSF, CDEmuTOC_FIRSTINDEX, CDEmuTOC_ENDOFDISC, CDEmuTOC_TRACKEND = 0x2000 };
extern TCHAR CDEmuImage[MAX_PATH];
extern UINT8 CDEmuImageTOCSHA1[MAX_PATH];