/**************************************************************************** * 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); }