Genesis-Plus-GX/core/cart_hw/megasd.c

1002 lines
32 KiB
C

/****************************************************************************
* Genesis Plus
* MegaSD flashcart CD hardware interface overlay & enhanced ROM mappers
*
* Copyright (C) 2020-2022 Eke-Eke (Genesis Plus GX)
*
* Redistribution and use of this code or any derivative works are permitted
* provided that the following conditions are met:
*
* - Redistributions may not be sold, nor may they be used in a commercial
* product or activity.
*
* - Redistributions that are modified from the original source must include the
* complete source code, including the source code for all components used by a
* binary built from the modified sources. However, as a special exception, the
* source code distributed need not include anything that is normally distributed
* (in either source or binary form) with the major components (compiler, kernel,
* and so on) of the operating system on which the executable runs, unless that
* component itself accompanies the executable.
*
* - Redistributions must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************************/
#include "shared.h"
typedef struct
{
uint8 unlock;
uint8 bank0;
uint8 special;
uint8 writeEnable;
uint8 overlayEnable;
uint8 playbackLoop;
uint8 playbackLoopTrack;
uint8 playbackEndTrack;
uint16 result;
uint16 fadeoutStartVolume;
int fadeoutSamplesTotal;
int fadeoutSamplesCount;
int playbackSamplesCount;
int playbackLoopSector;
int playbackEndSector;
uint8 buffer[0x800];
} T_MEGASD_HW;
/* MegaSD mapper hardware */
static T_MEGASD_HW megasd_hw;
/* Internal function prototypes */
static void megasd_ctrl_write_byte(unsigned int address, unsigned int data);
static void megasd_ctrl_write_word(unsigned int address, unsigned int data);
static unsigned int megasd_ctrl_read_byte(unsigned int address);
static unsigned int megasd_ctrl_read_word(unsigned int address);
static void megasd_pcm_write_byte(unsigned int address, unsigned int data);
static void megasd_pcm_write_word(unsigned int address, unsigned int data);
static unsigned int megasd_pcm_read_byte(unsigned int address);
static unsigned int megasd_pcm_read_word(unsigned int address);
/* default MegaSD version & serial number */
static const unsigned char megasd_version[16] = {'M','E','G','A','S','D',0x01,0x04,0x07,0x00,0xFF,0xFF,0x12,0x34,0x56,0x78};
/* MegaSD ID string (NB: there seems to be an error in MegaSD Dev manual as given hexadecimal values correspond to ASCII word "BATE", not "RATE") */
static const unsigned char megasd_id[4] = {0x42,0x41,0x54,0x45};
void megasd_reset(void)
{
/* reset MegaSD mapper */
memset(&megasd_hw, 0x00, sizeof (megasd_hw));
/* cartridge area bank #7 default mapping */
megasd_hw.special = 0x07;
/* CD hardware access overlay is mapped in 0x030000-0x03FFFF */
m68k.memory_map[0x03].read8 = megasd_ctrl_read_byte;
m68k.memory_map[0x03].read16 = megasd_ctrl_read_word;
m68k.memory_map[0x03].write8 = megasd_ctrl_write_byte;
m68k.memory_map[0x03].write16 = megasd_ctrl_write_word;
zbank_memory_map[0x03].read = megasd_ctrl_read_byte;
/* reset CD hardware (only if not already emulated) */
if (system_hw != SYSTEM_MCD)
{
pcm_reset();
cdd_reset();
scd.regs[0x36>>1].byte.h = 0x01;
}
}
int megasd_context_save(uint8 *state)
{
int bufferptr = 0;
save_param(&megasd_hw, sizeof(megasd_hw));
/* save needed CD hardware state (only if not already saved) */
if (system_hw != SYSTEM_MCD)
{
bufferptr += cdd_context_save(&state[bufferptr]);
bufferptr += pcm_context_save(&state[bufferptr]);
save_param(&scd.regs[0x36>>1].byte.h, 1);
}
return bufferptr;
}
int megasd_context_load(uint8 *state)
{
int bufferptr = 0;
load_param(&megasd_hw, sizeof(megasd_hw));
/* load needed CD hardware state (only if not already loaded) */
if (system_hw != SYSTEM_MCD)
{
bufferptr += cdd_context_load(&state[bufferptr], STATE_VERSION);
bufferptr += pcm_context_load(&state[bufferptr]);
load_param(&scd.regs[0x36>>1].byte.h, 1);
}
return bufferptr;
}
/*
Enhanced "SSF2" mapper
*/
void megasd_enhanced_ssf2_mapper_w(unsigned int address, unsigned int data)
{
int i, updateLastBank = 0;
switch (address & 0xf)
{
case 0x0:
{
/* check protect bit */
if (data & 0x80)
{
/* access to bank #0 register and ROM write enable bit is unlocked */
megasd_hw.unlock = 1;
/* ROM write enable bit */
megasd_hw.writeEnable = data & 0x20;
/* map bank #0 selected ROM page to $000000-$07ffff */
for (i=0x00; i<0x08; i++)
{
m68k.memory_map[i].base = cart.rom + (((megasd_hw.bank0 & 0x0f) << 19) & cart.mask) + (i<<16);
}
}
else
{
/* access to bank #0 register and ROM write enable bit is locked */
megasd_hw.unlock = 0;
/* disable ROM write enable access */
megasd_hw.writeEnable = 0;
/* reset default ROM mapping in $000000-$07ffff */
for (i=0x00; i<0x08; i++)
{
m68k.memory_map[i].base = cart.rom + (i<<16);
}
}
/* check ROM write enable status */
if (megasd_hw.writeEnable)
{
/* enable write access to cartridge ROM area ($000000-$37ffff) */
for (i=0x00; i<0x38; i++)
{
m68k.memory_map[i].write8 = NULL;
m68k.memory_map[i].write16 = NULL;
zbank_memory_map[i].write = NULL;
}
}
else
{
/* disable ROM write access to cartridge ROM area ($000000-$37ffff) */
for (i=0x00; i<0x38; i++)
{
m68k.memory_map[i].write8 = m68k_unused_8_w;
m68k.memory_map[i].write16 = m68k_unused_16_w;
zbank_memory_map[i].write = zbank_unused_w;
}
/* enable CD hardware overlay area access (it is assumed that overlay area is disabled when ROM write access is enabled) */
m68k.memory_map[0x03].write8 = megasd_ctrl_write_byte;
m68k.memory_map[0x03].write16 = megasd_ctrl_write_word;
}
/* force $380000-$3fffff mapping update */
updateLastBank = 1;
break;
}
case 0xf:
{
/* special bank register */
megasd_hw.special = data;
/* force $380000-$3fffff mapping update */
updateLastBank = 1;
break;
}
default:
{
/* LWR only */
if (address & 1)
{
/* 512K ROM paging (max. 8MB)*/
uint8 *src = cart.rom + (((data & 0x0f) << 19) & cart.mask);
/* cartridge area ($000000-$3FFFFF) is divided into 8 x 512K banks */
uint8 bank = (address << 2) & 0x38;
/* check selected bank is not locked */
if ((bank != 0x00) || megasd_hw.unlock)
{
/* selected ROM page is mapped to selected bank */
for (i=0; i<8; i++)
{
m68k.memory_map[bank + i].base = src + (i<<16);
}
}
}
else
{
m68k_unused_8_w(address, data);
}
break;
}
}
/* check if $380000-$3fffff mapping has to be updated */
if (updateLastBank)
{
/* check special bank register value */
if (megasd_hw.special == 0x80)
{
/* SRAM mapped in $380000-$3fffff (max 16KB) */
for (i=0x38; i<0x40; i++)
{
m68k.memory_map[i].base = sram.sram;
m68k.memory_map[i].read8 = sram_read_byte;
m68k.memory_map[i].read16 = sram_read_word;
m68k.memory_map[i].write8 = megasd_hw.writeEnable ? sram_write_byte : m68k_unused_8_w;
m68k.memory_map[i].write16 = megasd_hw.writeEnable ? sram_write_word : m68k_unused_16_w;
zbank_memory_map[i].read = sram_read_byte;
zbank_memory_map[i].write = megasd_hw.writeEnable ? sram_write_byte : zbank_unused_w;
}
}
else if (megasd_hw.special == 0x81)
{
/* PCM hardware mapped in $380000-$3fffff */
for (i=0x38; i<0x40; i++)
{
m68k.memory_map[i].base = NULL;
m68k.memory_map[i].read8 = megasd_pcm_read_byte;
m68k.memory_map[i].read16 = megasd_pcm_read_word;
m68k.memory_map[i].write8 = megasd_hw.writeEnable ? megasd_pcm_write_byte : m68k_unused_8_w;
m68k.memory_map[i].write16 = megasd_hw.writeEnable ? megasd_pcm_write_word : m68k_unused_16_w;
zbank_memory_map[i].read = megasd_pcm_read_byte;
zbank_memory_map[i].write = megasd_hw.writeEnable ? megasd_pcm_write_byte : zbank_unused_w;
}
}
else
{
/* 512K ROM paging (max. 8MB)*/
uint8 *src = cart.rom + (((megasd_hw.special & 0x0f) << 19) & cart.mask);
/* selected ROM page mapped in $380000-$3fffff */
for (i=0x38; i<0x40; i++)
{
m68k.memory_map[i].base = src + (i << 16);;
m68k.memory_map[i].read8 = NULL;
m68k.memory_map[i].read16 = NULL;
m68k.memory_map[i].write8 = megasd_hw.writeEnable ? NULL : m68k_unused_8_w;
m68k.memory_map[i].write16 = megasd_hw.writeEnable ? NULL : m68k_unused_16_w;
zbank_memory_map[i].read = NULL;
zbank_memory_map[i].write = megasd_hw.writeEnable ? NULL : zbank_unused_w;;
}
}
}
}
/*
ROM write access mapper
*/
void megasd_rom_mapper_w(unsigned int address, unsigned int data)
{
int i;
if ((address & 0xff) == 0xff)
{
if (data == 'W')
{
/* enable write access to cartridge ROM area ($000000-$3fffff) */
for (i=0; i<0x40; i++)
{
m68k.memory_map[i].write8 = NULL;
m68k.memory_map[i].write16 = NULL;
zbank_memory_map[i].write = NULL;
}
}
else
{
/* disable write access to cartridge ROM area ($000000-$3fffff) */
for (i=0; i<0x40; i++)
{
m68k.memory_map[i].write8 = m68k_unused_8_w;
m68k.memory_map[i].write16 = m68k_unused_16_w;
zbank_memory_map[i].write = zbank_unused_w;
}
/* enable CD hardware overlay access */
m68k.memory_map[0x03].write8 = megasd_ctrl_write_byte;
m68k.memory_map[0x03].write16 = megasd_ctrl_write_word;
}
}
else
{
m68k_unused_8_w(address, data);
}
}
/*
CDDA samples playback handler
*/
void megasd_update_cdda(unsigned int samples)
{
unsigned int count;
while (samples > 0)
{
/* check if audio playback is paused or stopped */
if (scd.regs[0x36>>1].byte.h == 0x01)
{
/* clear remaining needed CD-DA samples without updating counters */
cdd_read_audio(samples);
break;
}
/* attempt to read remaining needed samples by default */
count = samples;
/* check against fade out remaining samples */
if ((megasd_hw.fadeoutSamplesCount > 0) && (count > megasd_hw.fadeoutSamplesCount))
{
count = megasd_hw.fadeoutSamplesCount;
}
/* check against playback remaining samples */
if ((megasd_hw.playbackSamplesCount > 0) && (count > megasd_hw.playbackSamplesCount))
{
count = megasd_hw.playbackSamplesCount;
}
/* read required CD-DA samples */
cdd_read_audio(count);
/* adjust remaining needed samples count */
samples -= count;
/* check if fade out is still in progress */
if (megasd_hw.fadeoutSamplesCount > 0)
{
/* update remaining fade out samples count */
megasd_hw.fadeoutSamplesCount -= count;
/* check end of fade out */
if (megasd_hw.fadeoutSamplesCount <= 0)
{
/* pause audio playback */
scd.regs[0x36>>1].byte.h = 0x01;
cdd.status = CD_PAUSE;
/* restore initial volume */
cdd.fader[0] = cdd.fader[1] = megasd_hw.fadeoutStartVolume;
}
else
{
/* force volume for next frame */
cdd.fader[0] = cdd.fader[1] = (megasd_hw.fadeoutSamplesCount * megasd_hw.fadeoutStartVolume) / megasd_hw.fadeoutSamplesTotal;
}
}
/* Playback in progress ? */
if (megasd_hw.playbackSamplesCount > 0)
{
/* update remaining samples count */
megasd_hw.playbackSamplesCount -= count;
/* check end of current track */
if (megasd_hw.playbackSamplesCount <= 0)
{
/* check playback end track */
if (cdd.index < megasd_hw.playbackEndTrack)
{
/* seek start of next track */
cdd_seek_audio(cdd.index + 1, cdd.toc.tracks[cdd.index + 1].start);
/* increment current track index */
cdd.index++;
/* check if last track is being played */
if (cdd.index == megasd_hw.playbackEndTrack)
{
/* reinitialize remaining samples count using current track start sector and playback end sectors */
megasd_hw.playbackSamplesCount = (megasd_hw.playbackEndSector - cdd.toc.tracks[cdd.index].start) * 588;
}
else
{
/* reinitialize remaining samples count using current track start and end sectors */
megasd_hw.playbackSamplesCount = (cdd.toc.tracks[cdd.index].end - cdd.toc.tracks[cdd.index].start) * 588;
}
}
/* check track loop */
else if (megasd_hw.playbackLoop)
{
/* seek back to start track loop sector */
cdd_seek_audio(megasd_hw.playbackLoopTrack, megasd_hw.playbackLoopSector);
/* update current track index */
cdd.index = megasd_hw.playbackLoopTrack;
/* check if single track or successive tracks are being played */
if (cdd.index == megasd_hw.playbackEndTrack)
{
/* reinitialize remaining samples count using playback loop and end sectors */
megasd_hw.playbackSamplesCount = (megasd_hw.playbackEndSector - megasd_hw.playbackLoopSector) * 588;
}
else
{
/* reinitialize remaining samples count using playback loop sector and track end sector */
megasd_hw.playbackSamplesCount = (cdd.toc.tracks[cdd.index].end - megasd_hw.playbackLoopSector) * 588;
}
}
else
{
/* stop audio playback */
cdd.status = CD_STOP;
scd.regs[0x36>>1].byte.h = 0x01;
}
}
}
}
}
/*
CD hardware overlay interface
*/
static void megasd_ctrl_write_byte(unsigned int address, unsigned int data)
{
/* check if overlay area access is enabled */
if (megasd_hw.overlayEnable)
{
/* 2KB buffer area */
if (address >= 0x03f800)
{
megasd_hw.buffer[address & 0x7ff] = data;
return;
}
}
/* cartridge area write access is disabled by default */
m68k_unused_8_w(address, data);
}
static void megasd_ctrl_write_word(unsigned int address, unsigned int data)
{
/* overlay port (word write only) */
if (address == 0x03f7fa)
{
/* enable /disable CD hardware overlay access */
megasd_hw.overlayEnable = (data == 0xcd54) ? 1 : 0;
return;
}
/* check if overlay area access is enabled */
if (megasd_hw.overlayEnable)
{
/* command port (word write only) */
if (address == 0x03f7fe)
{
switch ((data >> 8) & 0xff)
{
case 0x10: /* Get MegaSD version & serial number */
{
memcpy(megasd_hw.buffer, megasd_version, sizeof(megasd_version));
return;
}
case 0x11: /* Play specified CDDA track and stop */
case 0x12: /* Play specified CDDA track and loop from start */
case 0x1a: /* Play specified CDDA track and loop from offset */
{
/* check a valid disc is loaded */
if (cdd.loaded)
{
/* get track index from command parameter */
int index = (data & 0xff) - 1;
/* check track index corresponds to a valid audio track */
if ((index >= 0) && (index < cdd.toc.last) && !cdd.toc.tracks[index].type)
{
/* initialize default playback info */
megasd_hw.playbackEndTrack = index;
megasd_hw.playbackEndSector = cdd.toc.tracks[index].end;
/* seek audio track start */
cdd_seek_audio(index, cdd.toc.tracks[index].start);
/* update current track index */
cdd.index = index;
/* indicate audio track is playing */
cdd.status = CD_PLAY;
scd.regs[0x36>>1].byte.h = 0x00;
/* check if fade out is still in progress */
if (megasd_hw.fadeoutSamplesCount > 0)
{
/* reset fade out */
megasd_hw.fadeoutSamplesCount = 0;
/* restore initial volume */
cdd.fader[0] = cdd.fader[1] = megasd_hw.fadeoutStartVolume;
}
/* initialize remaining samples count */
megasd_hw.playbackSamplesCount = (cdd.toc.tracks[index].end - cdd.toc.tracks[index].start) * 588;
/* check if there are any loop commands found in cue file for this track (see cdd_load function in cdd.c) */
if (cdd.toc.tracks[index].loopEnabled != 0)
{
/* cue file loop commands override programmed command loop settings */
megasd_hw.playbackLoop = cdd.toc.tracks[index].loopEnabled + 1;
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start + cdd.toc.tracks[index].loopOffset;
}
else
{
/* track loop is enabled for commands 12h and 1Ah / disabled for command 11h */
megasd_hw.playbackLoop = (data >> 8) & 0x02;
/* command 1Ah specifies track loop offset in data buffer (32-bit value stored in big-endian format) */
if ((data >> 8) == 0x1a)
{
#ifndef LSB_FIRST
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start + *(unsigned int *)(megasd_hw.buffer);
#else
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start + (megasd_hw.buffer[0] << 24) + (megasd_hw.buffer[1] << 16) + (megasd_hw.buffer[2] << 8) + megasd_hw.buffer[3];
#endif
}
else
{
/* default track loop offset */
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start;
}
}
/* check loop sector is within track limit */
if ((megasd_hw.playbackLoopSector < cdd.toc.tracks[index].start) || (megasd_hw.playbackLoopSector >= cdd.toc.tracks[index].end))
{
/* force default track loop offset */
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start;
}
/* initialize loop track index */
megasd_hw.playbackLoopTrack = index;
}
}
return;
}
case 0x13: /* Pause CDDA track */
{
/* check audio track is currently playing */
if (scd.regs[0x36>>1].byte.h == 0x00)
{
/* check if fade out is already in progress */
if (megasd_hw.fadeoutSamplesCount > 0)
{
/* restore initial volume */
cdd.fader[0] = cdd.fader[1] = megasd_hw.fadeoutStartVolume;
}
/* get fade out samples count from command parameter */
megasd_hw.fadeoutSamplesCount = (data & 0xff) * 588;
/* fade out enabled ? */
if (megasd_hw.fadeoutSamplesCount > 0)
{
/* save fade out sample count */
megasd_hw.fadeoutSamplesTotal = megasd_hw.fadeoutSamplesCount;
/* save current volume */
megasd_hw.fadeoutStartVolume = cdd.fader[0];
}
else
{
/* pause audio track playback immediately when fade out is disabled */
scd.regs[0x36>>1].byte.h = 0x01;
cdd.status = CD_PAUSE;
}
}
return;
}
case 0x14: /* Resume CDDA track */
{
/* check audio playback is currently paused */
if (cdd.status == CD_PAUSE)
{
scd.regs[0x36>>1].byte.h = 0x00;
cdd.status = CD_PLAY;
}
return;
}
case 0x15: /* Set CDDA volume (0-255) */
{
/* check if fade out is in progress */
if (megasd_hw.fadeoutSamplesCount > 0)
{
/* update default volume to be restored once fade out is finished */
megasd_hw.fadeoutStartVolume = ((data & 0xff) * 0x400) / 255;
}
else
{
/* update current volume */
cdd.fader[0] = cdd.fader[1] = ((data & 0xff) * 0x400) / 255;
}
return;
}
case 0x16: /* Get CDDA playback status */
{
megasd_hw.result = (scd.regs[0x36>>1].byte.h == 0x00) ? 0x01 : 0x00;
return;
}
case 0x17: /* Request CD sector read */
{
/* check a disc with valid data track is loaded */
if (cdd.loaded && cdd.toc.tracks[0].type)
{
/* get LBA from command buffer (32-bit value stored in big-endian format) */
#ifndef LSB_FIRST
int lba = *(unsigned int *)(megasd_hw.buffer) - 150;
#else
int lba = (megasd_hw.buffer[0] << 24) + (megasd_hw.buffer[1] << 16) + (megasd_hw.buffer[2] << 8) + megasd_hw.buffer[3] - 150;
#endif
/* only allow reading within first data track */
if (lba < cdd.toc.tracks[0].end)
{
/* set current LBA position */
cdd.lba = lba;
/* set current track index */
cdd.index = 0;
/* update CDD status to allow reading data track */
cdd.status = CD_PLAY;
/* no audio track playing */
scd.regs[0x36>>1].byte.h = 0x01;
}
}
return;
}
case 0x18: /* Transfer last read sector */
{
/* check a disc is loaded with a valid data track currently being read */
if (cdd.loaded && (cdd.status == CD_PLAY) && cdd.toc.tracks[cdd.index].type)
{
/* read sector data to 2K buffer */
cdd_read_data(megasd_hw.buffer, NULL);
}
return;
}
case 0x19: /* Request read of next sector */
{
/* check a disc is loaded with a valid data track currently being read */
if (cdd.loaded && (cdd.status == CD_PLAY) && cdd.toc.tracks[cdd.index].type)
{
/* only allow reading within first data track */
if (cdd.lba < (cdd.toc.tracks[0].end - 1))
{
/* increment current LBA position */
cdd.lba++;
}
}
return;
}
case 0x1b: /* Play CDDA from specific sector */
{
/* check a valid disc is loaded */
if (cdd.loaded)
{
/* get playback start sector from command buffer (32-bit value in big-endian format) */
#ifndef LSB_FIRST
int lba = *(unsigned int *)(megasd_hw.buffer) - 150;
#else
int lba = (megasd_hw.buffer[0] << 24) + (megasd_hw.buffer[1] << 16) + (megasd_hw.buffer[2] << 8) + megasd_hw.buffer[3] - 150;
#endif
/* get playback start track index */
int index = 0;
while ((cdd.toc.tracks[index].end <= lba) && (index < cdd.toc.last))
{
index++;
}
/* check start track index corresponds to a valid audio track */
if ((index < cdd.toc.last) && (cdd.toc.tracks[index].type == 0x00))
{
/* stay within track limits when seeking files */
if (lba < cdd.toc.tracks[index].start)
{
lba = cdd.toc.tracks[index].start;
}
/* seek audio track start offset */
cdd_seek_audio(index, lba);
/* update current track index */
cdd.index = index;
/* indicate audio track is playing */
cdd.status = CD_PLAY;
scd.regs[0x36>>1].byte.h = 0x00;
/* check if fade out is still in progress */
if (megasd_hw.fadeoutSamplesCount > 0)
{
/* reset fade out */
megasd_hw.fadeoutSamplesCount = 0;
/* restore initial volume */
cdd.fader[0] = cdd.fader[1] = megasd_hw.fadeoutStartVolume;
}
/* get playback end sector from command buffer (32-bit value in big-endian format) */
#ifndef LSB_FIRST
megasd_hw.playbackEndSector = *(unsigned int *)(megasd_hw.buffer + 4) - 150;
#else
megasd_hw.playbackEndSector = (megasd_hw.buffer[4] << 24) + (megasd_hw.buffer[5] << 16) + (megasd_hw.buffer[6] << 8) + megasd_hw.buffer[7] - 150;
#endif
/* check playback end sector is greater than start sector */
if (megasd_hw.playbackEndSector > lba)
{
/* get playback end track index */
megasd_hw.playbackEndTrack = index;
while ((cdd.toc.tracks[megasd_hw.playbackEndTrack].end <= megasd_hw.playbackEndSector) && (megasd_hw.playbackEndTrack < cdd.toc.last))
{
megasd_hw.playbackEndTrack++;
}
/* force playback end sector to end of last track when greater than last disc sector */
if (megasd_hw.playbackEndTrack == cdd.toc.last)
{
megasd_hw.playbackEndSector = cdd.toc.tracks[cdd.toc.last - 1].end;
megasd_hw.playbackEndTrack = cdd.toc.last - 1;
}
}
else
{
/* force playback end sector to end of playback start track otherwise */
megasd_hw.playbackEndSector = cdd.toc.tracks[index].end;
megasd_hw.playbackEndTrack = index;
}
/* check if a single track or successive tracks are played */
if (megasd_hw.playbackEndTrack == index)
{
/* initialize remaining samples count using playback start and end sectors */
megasd_hw.playbackSamplesCount = (megasd_hw.playbackEndSector - lba) * 588;
}
else
{
/* initialize remaining samples count using playback start sector and start track last sector */
megasd_hw.playbackSamplesCount = (cdd.toc.tracks[index].end - lba) * 588;
}
/* check if there are any loop commands found in cue file for this track (see cdd_load function in cdd.c) */
if (cdd.toc.tracks[index].loopEnabled != 0)
{
/* cue file loop commands override programmed command loop settings */
megasd_hw.playbackLoop = cdd.toc.tracks[index].loopEnabled + 1;
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start + cdd.toc.tracks[index].loopOffset;
}
else
{
/* get track loop setting from command parameter */
megasd_hw.playbackLoop = data & 0x01;
/* track loop enabled ? */
if (megasd_hw.playbackLoop)
{
/* get playback loop sector from data buffer (32-bit value in big-endian format) */
#ifndef LSB_FIRST
megasd_hw.playbackLoopSector = *(unsigned int *)(megasd_hw.buffer + 8) - 150;
#else
megasd_hw.playbackLoopSector = (megasd_hw.buffer[8] << 24) + (megasd_hw.buffer[9] << 16) + (megasd_hw.buffer[10] << 8) + megasd_hw.buffer[11] - 150;
#endif
}
else
{
/* set default track loop offset */
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start;
}
}
/* check loop sector is within start track limits */
if ((megasd_hw.playbackLoopSector < cdd.toc.tracks[index].start) || (megasd_hw.playbackLoopSector >= cdd.toc.tracks[index].end) || (megasd_hw.playbackLoopSector >= megasd_hw.playbackEndSector))
{
/* force default track loop offset */
megasd_hw.playbackLoopSector = cdd.toc.tracks[index].start;
}
/* initialize loop track index */
megasd_hw.playbackLoopTrack = index;
}
}
return;
}
case 0x1C: /* Start reading from a file */
case 0x1D: /* Read data from file to ROM */
case 0x1E: /* Read directory files to ROM */
case 0x1F: /* Play WAV file */
case 0x20: /* Read 2K data block from file to internal buffer */
case 0x21: /* Read next 2K data block from file to internal buffer */
{
/* unsupported commands */
megasd_hw.result = 0;
return;
}
default:
{
/* invalid command */
m68k_unused_16_w(address, data);
return;
}
}
}
/* 2KB buffer area */
if (address >= 0x03f800)
{
WRITE_WORD(megasd_hw.buffer, address & 0x7fe, data);
return;
}
}
/* cartridge area write access is disabled by default */
m68k_unused_16_w(address, data);
}
static unsigned int megasd_ctrl_read_byte(unsigned int address)
{
/* check if overlay area access is enabled */
if (megasd_hw.overlayEnable)
{
/* ID port */
if ((address >= 0x3f7f6) && (address <= 0x3f7f9))
{
return megasd_id[address & 0x03];
}
/* Overlay port */
if ((address >= 0x3f7fa) && (address <= 0x3f7fb))
{
return (address & 1) ? 0x54 :0xcd;
}
/* Result port */
if ((address >= 0x3f7fc) && (address <= 0x3f7fd))
{
return (address & 1) ? (megasd_hw.result & 0xff) : (megasd_hw.result >> 8);
}
/* Command port */
if ((address >= 0x3f7fe) && (address <= 0x3f7ff))
{
/* commands processing time is not emulated */
return 0x00;
}
/* 2KB buffer area */
if (address >= 0x03f800)
{
return megasd_hw.buffer[address & 0x7ff];
}
}
/* default cartridge area */
return READ_BYTE(m68k.memory_map[0x03].base, address & 0xffff);
}
static unsigned int megasd_ctrl_read_word(unsigned int address)
{
/* check if overlay area access is enabled */
if (megasd_hw.overlayEnable)
{
/* ID port */
if ((address == 0x3f7f6) || (address == 0x3f7f8))
{
return READ_WORD(megasd_id, address - 0x3f7f6);
}
/* Overlay port */
if (address == 0x3f7fa)
{
return 0xcd54;
}
/* Result port */
if (address == 0x3f7fc)
{
return megasd_hw.result;
}
/* Command port */
if (address == 0x3f7fe)
{
/* commands processing time is not emulated */
return 0x0000;
}
/* 2KB buffer area */
if (address >= 0x03f800)
{
return READ_WORD(megasd_hw.buffer, address & 0x7fe);
}
}
/* default cartridge area */
return *(uint16 *)(m68k.memory_map[0x03].base + (address & 0xfffe));
}
/*
PCM sound chip interface
*/
static void megasd_pcm_write_byte(unsigned int address, unsigned int data)
{
/* /LDS only */
if (address & 1)
{
pcm_write((address >> 1) & 0x1fff, data, (m68k.cycles * SCYCLES_PER_LINE) / MCYCLES_PER_LINE);
return;
}
m68k_unused_8_w(address, data);
return;
}
static void megasd_pcm_write_word(unsigned int address, unsigned int data)
{
/* /LDS only */
pcm_write((address >> 1) & 0x1fff, data & 0xff, (m68k.cycles * SCYCLES_PER_LINE) / MCYCLES_PER_LINE);
}
static unsigned int megasd_pcm_read_byte(unsigned int address)
{
/* /LDS only */
if (address & 1)
{
return pcm_read((address >> 1) & 0x1fff, (m68k.cycles * SCYCLES_PER_LINE) / MCYCLES_PER_LINE);
}
return 0x00;
}
static unsigned int megasd_pcm_read_word(unsigned int address)
{
/* /LDS only */
return pcm_read((address >> 1) & 0x1fff, (m68k.cycles * SCYCLES_PER_LINE) / MCYCLES_PER_LINE);
}