// license:LGPL-2.1+ // copyright-holders:Angelo Salese, R. Belmont /*************************************************************************** machine/stvcd.c - Sega Saturn and ST-V CD-ROM handling Another tilt at the windmill in 2011 by R. Belmont. Status: All known discs at least load their executable, and many load some data files successfully, but there are other problems. Information sources: - Tyranid's document - A commented disassembly I made of the Saturn BIOS's CD code - Yabuse's cs2.c - The ISO/IEC "Yellow Book" CD-ROM standard, 1995 version Address is mostly in terms of FAD (Frame ADdress). FAD is absolute number of frames from the start of the disc. In other words, FAD = LBA + 150; FAD is the same units as LBA except it counts starting at absolute zero instead of the first sector (00:02:00 in MSF format). ***************************************************************************/ #include "emu.h" #include "imagedev/chd_cd.h" #include "includes/saturn.h" #include "cdrom.h" #include "sound/cdda.h" #include "debugger.h" #include "coreutil.h" // super-verbose #if 0 #define CDROM_LOG(x) printf x; #else #define CDROM_LOG(x) #endif // HIRQ definitions #define CMOK 0x0001 // command dispatch possible #define DRDY 0x0002 // data transfer preparations complete #define CSCT 0x0004 // finished reading 1 sector #define BFUL 0x0008 // CD buffer full #define PEND 0x0010 // CD playback completed #define DCHG 0x0020 // disc change / tray open #define ESEL 0x0040 // selector settings processing complete #define EHST 0x0080 // host input/output processing complete #define ECPY 0x0100 // duplication/move processing complete #define EFLS 0x0200 // file system processing complete #define SCDQ 0x0400 // subcode Q update completed #define MPED 0x0800 // MPEG-related processing complete #define MPCM 0x1000 // MPEG action uncertain #define MPST 0x2000 // MPEG interrupt status report // CD status (hi byte of CR1) definitions: // (these defines are shifted up 8) #define CD_STAT_BUSY 0x0000 // status change in progress #define CD_STAT_PAUSE 0x0100 // CD block paused (temporary stop) #define CD_STAT_STANDBY 0x0200 // CD drive stopped #define CD_STAT_PLAY 0x0300 // CD play in progress #define CD_STAT_SEEK 0x0400 // drive seeking #define CD_STAT_SCAN 0x0500 // drive scanning #define CD_STAT_OPEN 0x0600 // tray is open #define CD_STAT_NODISC 0x0700 // no disc present #define CD_STAT_RETRY 0x0800 // read retry in progress #define CD_STAT_ERROR 0x0900 // read data error occurred #define CD_STAT_FATAL 0x0a00 // fatal error (hard reset required) #define CD_STAT_PERI 0x2000 // periodic response if set, else command response #define CD_STAT_TRANS 0x4000 // data transfer request if set #define CD_STAT_WAIT 0x8000 // waiting for command if set, else executed immediately #define CD_STAT_REJECT 0xff00 // ultra-fatal error. int saturn_state::get_timing_command(void) { /* TODO: calculate timings based off command params */ return 16667; } /* FIXME: assume Saturn CD-ROMs to have a 2 secs pre-gap for now. */ int saturn_state::get_track_index(uint32_t fad) { uint32_t rel_fad; uint8_t track; if(cdrom_get_track_type(cdrom, cdrom_get_track(cdrom, fad)) != CD_TRACK_AUDIO) return 1; track = cdrom_get_track( cdrom, fad ); rel_fad = fad - cdrom_get_track_start( cdrom, track ); if(rel_fad < 150) return 0; return 1; } int saturn_state::sega_cdrom_get_adr_control(cdrom_file *file, int track) { return BITSWAP8(cdrom_get_adr_control(file, cur_track),3,2,1,0,7,6,5,4); } void saturn_state::cr_standard_return(uint16_t cur_status) { if ((cd_stat & 0x0f00) == CD_STAT_SEEK) { /* During seek state, values returned are from the target position */ uint8_t seek_track = cdrom_get_track(cdrom, cd_fad_seek-150); cr1 = cur_status | (playtype << 7) | 0x00 | (cdda_repeat_count & 0xf); cr2 = (seek_track == 0xff) ? 0xffff : ((sega_cdrom_get_adr_control(cdrom, seek_track)<<8) | seek_track); cr3 = (get_track_index(cd_fad_seek)<<8) | (cd_fad_seek>>16); //index & 0xff00 cr4 = cd_fad_seek; } else { /* TODO: - Whizz: wpset 0x608f030,4,w,wpdata==0x100&&pc!=0x6040006 */ cr1 = cur_status | (playtype << 7) | 0x00 | (cdda_repeat_count & 0xf); //options << 4 | repeat & 0xf cr2 = (cur_track == 0xff) ? 0xffff : ((sega_cdrom_get_adr_control(cdrom, cur_track)<<8) | (cdrom_get_track(cdrom, cd_curfad-150)+1)); cr3 = (get_track_index(cd_curfad)<<8) | (cd_curfad>>16); //index & 0xff00 cr4 = cd_curfad; } } void saturn_state::cd_exec_command( void ) { uint32_t temp; if(cr1 != 0 && ((cr1 & 0xff00) != 0x5100) && ((cr1 & 0xff00) != 0x5200) && ((cr1 & 0xff00) != 0x5300) && 1) logerror("CD: command exec %04x %04x %04x %04x %04x (stat %04x)\n", hirqreg, cr1, cr2, cr3, cr4, cd_stat); switch ((cr1 >> 8) & 0xff) { case 0x00: //CDROM_LOG(("%s:CD: Get Status\n", machine().describe_context())) hirqreg |= CMOK; if(status_type == 0) cr_standard_return(cd_stat); else { cr1 = (cd_stat) | (prev_cr1 & 0xff); cr2 = prev_cr2; cr3 = prev_cr3; cr4 = prev_cr4; status_type = 0; /* Road Blaster and friends needs this otherwise they won't boot. */ } //CDROM_LOG((" = %04x %04x %04x %04x %04x\n", hirqreg, cr1, cr2, cr3, cr4)) break; case 0x01: CDROM_LOG(("%s:CD: Get Hardware Info\n", machine().describe_context())) hirqreg |= CMOK; cr1 = cd_stat; cr2 = 0x0201; cr3 = 0x0000; cr4 = 0x0400; status_type = 0; break; case 0x02: // Get TOC CDROM_LOG(("%s:CD: Get TOC\n", machine().describe_context())) cd_readTOC(); cd_stat = CD_STAT_TRANS|CD_STAT_PAUSE; cr1 = cd_stat; cr2 = 102*2; // TOC length in words (102 entries @ 2 words/4bytes each) cr3 = 0; cr4 = 0; xferdnum = 0; hirqreg |= (CMOK|DRDY); status_type = 0; break; case 0x03: // get session info (lower byte = session # to get?) // bios is interested in returns in cr3 and cr4 // cr3 should be data track # // cr4 must be > 1 and < 100 or bios gets angry. CDROM_LOG(("%s:CD: Get Session Info\n", machine().describe_context())) cd_readTOC(); switch (cr1 & 0xff) { case 0: // get total session info / disc end cd_stat = CD_STAT_PAUSE; cr1 = cd_stat; cr2 = 0; cr3 = 0x0100 | tocbuf[(101*4)+1]; cr4 = tocbuf[(101*4)+2]<<8 | tocbuf[(101*4)+3]; break; case 1: // get total session info / disc start cd_stat = CD_STAT_PAUSE; cr1 = cd_stat; cr2 = 0; cr3 = 0x0100; // sessions in high byte, session start in lower cr4 = 0; break; default: osd_printf_error("CD: Unknown request to Get Session Info %x\n", cr1 & 0xff); cr1 = cd_stat; cr2 = 0; cr3 = 0; cr4 = 0; break; } hirqreg |= (CMOK); status_type = 0; break; /* TODO: double check this */ case 0x04: // initialize CD system // CR1 & 1 = reset software // CR1 & 2 = decode RW subcode // CR1 & 4 = don't confirm mode 2 subheader // CR1 & 8 = retry reading mode 2 sectors // CR1 & 10 = force single-speed // CR1 & 80 = no change flag (done by Assault Suit Leynos 2) CDROM_LOG(("%s:CD: Initialize CD system\n", machine().describe_context())) //if((cr1 & 0x81) == 0x00) //guess TODO: nope, Choice Cuts doesn't like it, it crashes if you try to skip the FMV otherwise. { if(((cd_stat & 0x0f00) != CD_STAT_NODISC) && ((cd_stat & 0x0f00) != CD_STAT_OPEN)) { cd_stat = CD_STAT_PAUSE; cd_curfad = 150; //cur_track = 1; fadstoplay = 0; } in_buffer = 0; buffull = 0; hirqreg &= 0xffe5; cd_speed = (cr1 & 0x10) ? 1 : 2; /* reset filter connections */ /* Guess: X-Men COTA sequence is 0x48->0x48->0x04(01)->0x04(00)->0x30 then 0x10, without this game throws a FAD reject error */ /* X-Men vs. SF is even fussier, sequence is 0x04 (1) 0x04 (0) 0x03 (0) 0x03 (1) 0x30 */ #if 0 for(int i=0;i>17) & 0xff); cr2 = (xferdnum>>1)&0xffff; cr3 = 0; cr4 = 0; } else { logerror("No xferdnum error\n"); cr1 = (cd_stat) | (0xff); // is this right? cr2 = 0xffff; cr3 = 0; cr4 = 0; } // try to clean up any transfers still in progress switch (xfertype32) { case XFERTYPE32_GETSECTOR: case XFERTYPE32_PUTSECTOR: hirqreg |= EHST; break; case XFERTYPE32_GETDELETESECTOR: if (transpart->size > 0) { int32_t i; xfertype32 = XFERTYPE32_INVALID; // deallocate the blocks for (i = xfersectpos; i < xfersectpos+xfersectnum; i++) { cd_free_block(transpart->blocks[i]); transpart->blocks[i] = (blockT *)nullptr; transpart->bnum[i] = 0xff; } // defrag what's left cd_defragblocks(transpart); // clean up our state transpart->size -= xferdnum; transpart->numblks -= xfersectnum; if (freeblocks == 200) { sectorstore = 0; } hirqreg |= EHST; } break; default: break; } xferdnum = 0; hirqreg |= CMOK; CDROM_LOG((" = %04x %04x %04x %04x %04x\n", hirqreg, cr1, cr2, cr3, cr4)) status_type = 1; break; case 0x10: // Play Disc. FAD is in lowest 7 bits of cr1 and all of cr2. uint32_t start_pos,end_pos; uint8_t play_mode; CDROM_LOG(("%s:CD: Play Disc\n", machine().describe_context())) cd_stat = CD_STAT_PLAY; play_mode = (cr3 >> 8) & 0x7f; if (!(cr3 & 0x8000)) // preserve current position if bit 7 set { start_pos = ((cr1&0xff)<<16) | cr2; end_pos = ((cr3&0xff)<<16) | cr4; if (start_pos & 0x800000) { if (start_pos != 0xffffff) cd_curfad = start_pos & 0xfffff; logerror("fad mode\n"); cur_track = cdrom_get_track(cdrom, cd_curfad-150); } else { // track mode if(((start_pos)>>8) != 0) { cur_track = (start_pos)>>8; cd_fad_seek = cdrom_get_track_start(cdrom, cur_track-1); cd_stat = CD_STAT_SEEK; machine().device("cdda")->pause_audio(0); } else { /* TODO: Waku Waku 7 sets up track 0, that basically doesn't make any sense. Just skip it for now. */ popmessage("Warning: track mode == 0, contact MAMEdev"); cr_standard_return(cd_stat); hirqreg |= (CMOK); return; } printf("track mode %d\n",cur_track); } if (end_pos & 0x800000) { if (end_pos != 0xffffff) fadstoplay = end_pos & 0xfffff; } else { uint8_t end_track; end_track = (end_pos)>>8; fadstoplay = cdrom_get_track_start(cdrom, end_track) - cd_fad_seek; } } else // play until the end of the disc { start_pos = ((cr1&0xff)<<16) | cr2; end_pos = ((cr3&0xff)<<16) | cr4; if(start_pos != 0xffffff) { /* Madou Monogatari sets 0xff80xxxx as end position, needs investigation ... */ if(end_pos & 0x800000) fadstoplay = end_pos & 0xfffff; else { if(end_pos == 0) fadstoplay = (cdrom_get_track_start(cdrom, 0xaa)) - cd_curfad; else fadstoplay = (cdrom_get_track_start(cdrom, (end_pos & 0xff00) >> 8)) - cd_curfad; } logerror("track mode %08x %08x\n",cd_curfad,fadstoplay); } else { /* resume from a pause state */ /* TODO: Galaxy Fight calls 10ff ffff ffff ffff, but then it calls 0x04->0x02->0x06->0x11->0x04->0x02->0x06 command sequence (and current implementation nukes start/end FAD addresses at 0x04). I'm sure that this doesn't work like this, but there could be countless possible combinations ... */ if(fadstoplay == 0) { cd_curfad = cdrom_get_track_start(cdrom, cur_track-1); fadstoplay = cdrom_get_track_start(cdrom, cur_track) - cd_curfad; } logerror("track resume %08x %08x\n",cd_curfad,fadstoplay); } } CDROM_LOG(("CD: Play Disc: start %x length %x\n", cd_curfad, fadstoplay)) cr_standard_return(cd_stat); hirqreg |= (CMOK); oddframe = 0; in_buffer = 0; playtype = 0; // cdda if(cdrom_get_track_type(cdrom, cdrom_get_track(cdrom, cd_curfad)) == CD_TRACK_AUDIO) { machine().device("cdda")->pause_audio(0); //machine().device("cdda")->start_audio(cd_curfad, fadstoplay); //cdda_repeat_count = 0; } if(play_mode != 0x7f) cdda_maxrepeat = play_mode & 0xf; else cdda_maxrepeat = 0; cdda_repeat_count = 0; status_type = 0; break; case 0x11: // disc seek CDROM_LOG(("%s:CD: Disc seek\n", machine().describe_context())) //printf("%08x %08x %08x %08x\n",cr1,cr2,cr3,cr4); if (cr1 & 0x80) { temp = (cr1&0xff)<<16; // get FAD to seek to temp |= cr2; //cd_curfad = temp; if (temp == 0xffffff) { cd_stat = CD_STAT_PAUSE; machine().device("cdda")->pause_audio(1); } else { cd_curfad = ((cr1&0x7f)<<16) | cr2; printf("disc seek with params %04x %04x\n",cr1,cr2); //Area 51 sets this up } } else { // is it a valid track? if (cr2 >> 8) { cd_stat = CD_STAT_PAUSE; cur_track = cr2>>8;; cd_curfad = cdrom_get_track_start(cdrom, cur_track-1); machine().device("cdda")->pause_audio(1); // (index is cr2 low byte) } else // error! { cd_stat = CD_STAT_STANDBY; cd_curfad = 0xffffffff; cur_track = 0xff; machine().device("cdda")->stop_audio(); //stop any pending CD-DA } } hirqreg |= CMOK; cr_standard_return(cd_stat); status_type = 0; break; case 0x12: // FFWD / REW //cr1 bit 0 determines if this is a Fast Forward (0) or a Rewind (1) command // ... break; case 0x20: // Get SubCode Q / RW Channel switch(cr1 & 0xff) { case 0: // Get Q { uint32_t msf_abs,msf_rel; uint8_t track; cr1 = cd_stat | 0; cr2 = 10/2; cr3 = 0; cr4 = 0; /* Subcode Q info should be: ---- --x- S0 ---- ---x S1 xxxx ---- [0] Control (bit 7 Pre-emphasis, bit 6: copy permitted, bit 5 undefined, bit 4 number of channels) ---- xxxx [0] address (0x0001 Mode 1) xxxx xxxx [1] track number (1-99, AA lead-out), BCD format xxxx xxxx [2] index (01 lead-out), BCD format xxxx xxxx [3] Time within' track M xxxx xxxx [4] Time within' track S xxxx xxxx [5] Time within' track F xxxx xxxx [6] Zero xxxx xxxx [7] Absolute M xxxx xxxx [8] Absolute S xxxx xxxx [9] Absolute F xxxx xxxx [10] CRCC xxxx xxxx [11] CRCC */ msf_abs = lba_to_msf_alt( cd_curfad - 150 ); track = cdrom_get_track( cdrom, cd_curfad ); msf_rel = lba_to_msf_alt( cd_curfad - 150 - cdrom_get_track_start( cdrom, track ) ); xfertype = XFERTYPE_SUBQ; xfercount = 0; subqbuf[0] = 0x01 | ((cdrom_get_track_type(cdrom, cdrom_get_track(cdrom, track+1)) == CD_TRACK_AUDIO) ? 0x00 : 0x40); subqbuf[1] = dec_2_bcd(track+1); subqbuf[2] = dec_2_bcd(get_track_index(cd_curfad)); subqbuf[3] = dec_2_bcd((msf_rel >> 16) & 0xff); subqbuf[4] = dec_2_bcd((msf_rel >> 8) & 0xff); subqbuf[5] = dec_2_bcd((msf_rel >> 0) & 0xff); subqbuf[6] = 0; subqbuf[7] = dec_2_bcd((msf_abs >> 16) & 0xff); subqbuf[8] = dec_2_bcd((msf_abs >> 8) & 0xff); subqbuf[9] = dec_2_bcd((msf_abs >> 0) & 0xff); } break; case 1: // Get RW cr1 = cd_stat | 0; cr2 = 12; cr3 = 0; cr4 = 0; xfertype = XFERTYPE_SUBRW; xfercount = 0; /* return null data for now */ { int i; for(i=0;i<12*2;i++) subrwbuf[i] = 0; } break; } hirqreg |= CMOK|DRDY; status_type = 0; break; case 0x30: // Set CD Device connection { uint8_t parm; // get operation parm = cr3>>8; CDROM_LOG(("%s:CD: Set CD Device Connection filter # %x\n", machine().describe_context(), parm)) cddevicenum = parm; if (parm == 0xff) { cddevice = (filterT *)nullptr; } else { if (parm < 0x24) { cddevice = &filters[(cr3>>8)]; } } hirqreg |= (CMOK|ESEL); cr_standard_return(cd_stat); status_type = 0; } break; case 0x31: popmessage("Get CD Device Connection, contact MAMEdev"); hirqreg |= CMOK; break; case 0x32: // Last Buffer Destination cr1 = cd_stat | 0; cr2 = 0; cr3 = lastbuf << 8; cr4 = 0; hirqreg |= (CMOK); status_type = 0; break; case 0x40: // Set Filter Range // cr1 low + cr2 = FAD0, cr3 low + cr4 = FAD1 // cr3 hi = filter num. { uint8_t fnum = (cr3>>8)&0xff; CDROM_LOG(("%s:CD: Set Filter Range\n", machine().describe_context())) filters[fnum].fad = ((cr1 & 0xff)<<16) | cr2; filters[fnum].range = ((cr3 & 0xff)<<16) | cr4; printf("%08x %08x %d\n",filters[fnum].fad,filters[fnum].range,fnum); hirqreg |= (CMOK|ESEL); cr_standard_return(cd_stat); status_type = 0; } break; case 0x41: popmessage("Get Filter Range, contact MAMEdev"); hirqreg |= CMOK; break; case 0x42: // Set Filter Subheader conditions { uint8_t fnum = (cr3>>8)&0xff; CDROM_LOG(("%s:CD: Set Filter Subheader conditions %x => chan %x masks %x fid %x vals %x\n", machine().describe_context(), fnum, cr1&0xff, cr2, cr3&0xff, cr4)) filters[fnum].chan = cr1 & 0xff; filters[fnum].smmask = (cr2>>8)&0xff; filters[fnum].cimask = cr2&0xff; filters[fnum].fid = cr3&0xff; filters[fnum].smval = (cr4>>8)&0xff; filters[fnum].cival = cr4&0xff; hirqreg |= (CMOK|ESEL); cr_standard_return(cd_stat); status_type = 0; } break; case 0x43: // Get Filter Subheader conditions { uint8_t fnum = (cr3>>8)&0xff; CDROM_LOG(("%s:CD: Set Filter Subheader conditions %x => chan %x masks %x fid %x vals %x\n", machine().describe_context(), fnum, cr1&0xff, cr2, cr3&0xff, cr4)) cr1 = cd_stat | (filters[fnum].chan & 0xff); cr2 = (filters[fnum].smmask << 8) | (filters[fnum].cimask & 0xff); cr3 = filters[fnum].fid; cr4 = (filters[fnum].smval << 8) | (filters[fnum].cival & 0xff); hirqreg |= (CMOK|ESEL); status_type = 0; } break; case 0x44: // Set Filter Mode { uint8_t fnum = (cr3>>8)&0xff; uint8_t mode = (cr1 & 0xff); // initialize filter? if (mode & 0x80) { memset(&filters[fnum], 0, sizeof(filterT)); } else { filters[fnum].mode = mode; } CDROM_LOG(("%s:CD: Set Filter Mode filt %x mode %x\n", machine().describe_context(), fnum, mode)) hirqreg |= (CMOK|ESEL); cr_standard_return(cd_stat); status_type = 0; } break; case 0x45: // Get Filter Mode { uint8_t fnum = (cr3>>8)&0xff; cr1 = cd_stat | (filters[fnum].mode & 0xff); cr2 = 0; cr3 = 0; cr4 = 0; hirqreg |= (CMOK|ESEL); status_type = 0; } break; case 0x46: // Set Filter Connection { /* TODO: maybe condition false is cr3 low? */ uint8_t fnum = (cr3>>8)&0xff; CDROM_LOG(("%s:CD: Set Filter Connection %x => mode %x parm %04x\n", machine().describe_context(), fnum, cr1 & 0xf, cr2)) if (cr1 & 1) // set true condition filters[fnum].condtrue = (cr2>>8)&0xff; if (cr1 & 2) // set false condition filters[fnum].condfalse = cr2&0xff; hirqreg |= (CMOK|ESEL); cr_standard_return(cd_stat); status_type = 0; } break; case 0x48: // Reset Selector { int i,j; CDROM_LOG(("%s:CD: Reset Selector\n", machine().describe_context())) if((cr1 & 0xff) == 0x00) { uint8_t bufnum = cr3>>8; if(bufnum < MAX_FILTERS) { for (i = 0; i < MAX_BLOCKS; i++) { cd_free_block(partitions[bufnum].blocks[i]); partitions[bufnum].blocks[i] = (blockT *)nullptr; partitions[bufnum].bnum[i] = 0xff; } partitions[bufnum].size = -1; partitions[bufnum].numblks = 0; } // TODO: buffer full flag if (freeblocks == 200) { sectorstore = 0; } hirqreg |= (CMOK|ESEL); cr_standard_return(cd_stat); status_type = 0; return; } /* reset false filter output conditions */ /* TODO: check these two. */ if(cr1 & 0x80) { for(i=0;i 200) ? 200 : freeblocks; cr3 = 0x1800; cr4 = 200; CDROM_LOG(("CD: Get Buffer Size = %d\n", cr2)) hirqreg |= (CMOK); status_type = 0; break; case 0x51: // get # sectors used in a buffer { uint32_t bufnum = cr3>>8; CDROM_LOG(("%s:CD: Get Sector Number (bufno %d) = %d blocks\n", machine().describe_context(), bufnum, cr4)) cr1 = cd_stat; cr2 = 0; cr3 = 0; if(cr1 & 0xff || cr2 || cr3 & 0xff || cr4) printf("Get # sectors used with params %04x %04x %04x %04x\n",cr1,cr2,cr3,cr4); // is the partition empty? if (partitions[bufnum].size == -1) { cr4 = 0; } else { cr4 = partitions[bufnum].numblks; //printf("Partition %08x %04x\n",bufnum,cr4); } //printf("%04x\n",cr4); if(cr4 == 0) hirqreg |= (CMOK); else hirqreg |= (CMOK|DRDY); status_type = 1; } break; case 0x52: // calculate actual size { uint32_t bufnum = cr3>>8; uint32_t sectoffs = cr2; uint32_t numsect = cr4; CDROM_LOG(("%s:CD: Calculate actual size: buf %x offs %x numsect %x\n", machine().describe_context(), bufnum, sectoffs, numsect)) calcsize = 0; if (partitions[bufnum].size != -1) { int32_t i; for (i = 0; i < numsect; i++) { if (partitions[bufnum].blocks[sectoffs+i]) { calcsize += (partitions[bufnum].blocks[sectoffs+i]->size / 2); } } } hirqreg |= (CMOK|ESEL); cr_standard_return(cd_stat); status_type = 0; } break; case 0x53: // get actual block size CDROM_LOG(("%s:CD: Get actual block size\n", machine().describe_context())) cr1 = cd_stat | ((calcsize>>16)&0xff); cr2 = (calcsize & 0xffff); cr3 = 0; cr4 = 0; hirqreg |= (CMOK|ESEL); status_type = 1; break; case 0x54: // get sector info { uint32_t sectoffs = cr2 & 0xff; uint32_t bufnum = cr3>>8; if (bufnum >= MAX_FILTERS || !partitions[bufnum].blocks[sectoffs]) { cr1 |= CD_STAT_REJECT & 0xff00; hirqreg |= (CMOK|ESEL); printf("Get sector info reject\n"); } else { cr1 = cd_stat | ((partitions[bufnum].blocks[sectoffs]->FAD >> 16) & 0xff); cr2 = partitions[bufnum].blocks[sectoffs]->FAD & 0xffff; cr3 = ((partitions[bufnum].blocks[sectoffs]->fnum & 0xff) << 8) | (partitions[bufnum].blocks[sectoffs]->chan & 0xff); cr4 = ((partitions[bufnum].blocks[sectoffs]->subm & 0xff) << 8) | (partitions[bufnum].blocks[sectoffs]->cinf & 0xff); hirqreg |= (CMOK|ESEL); } status_type = 0; } break; case 0x60: // set sector length CDROM_LOG(("%s:CD: Set sector length\n", machine().describe_context())) switch (cr1 & 0xff) { case 0: sectlenin = 2048; break; case 1: sectlenin = 2336; break; case 2: sectlenin = 2340; break; case 3: sectlenin = 2352; break; } switch ((cr2>>8) & 0xff) { case 0: sectlenout = 2048; break; case 1: sectlenout = 2336; break; case 2: sectlenout = 2340; break; case 3: sectlenout = 2352; break; } hirqreg |= (CMOK|ESEL); cr_standard_return(cd_stat); status_type = 0; break; case 0x61: // get sector data { uint32_t sectnum = cr4; uint32_t sectofs = cr2; uint32_t bufnum = cr3>>8; CDROM_LOG(("%s:CD: Get sector data (SN %d SO %d BN %d)\n", machine().describe_context(), sectnum, sectofs, bufnum)) if (bufnum >= MAX_FILTERS) { printf("CD: invalid buffer number\n"); /* TODO: why this is happening? */ cr_standard_return(CD_STAT_REJECT); hirqreg |= (CMOK|EHST); return; } if (partitions[bufnum].numblks < sectnum) { printf("CD: buffer is not full %08x %08x\n",partitions[bufnum].numblks,sectnum); cr_standard_return(CD_STAT_REJECT); hirqreg |= (CMOK|EHST); return; } cd_getsectoroffsetnum(bufnum, §ofs, §num); xfertype32 = XFERTYPE32_GETSECTOR; xferoffs = 0; xfersect = 0; xferdnum = 0; xfersectpos = sectofs; xfersectnum = sectnum; transpart = &partitions[bufnum]; cd_stat |= CD_STAT_TRANS; cr_standard_return(cd_stat); hirqreg |= (CMOK|EHST|DRDY); status_type = 0; } break; case 0x62: // delete sector data { uint32_t sectnum = cr4; uint32_t sectofs = cr2; uint32_t bufnum = cr3>>8; int32_t i; CDROM_LOG(("%s:CD: Delete sector data (SN %d SO %d BN %d)\n", machine().describe_context(), sectnum, sectofs, bufnum)) if (bufnum >= MAX_FILTERS) { printf("CD: invalid buffer number\n"); /* TODO: why this is happening? */ cr_standard_return(CD_STAT_REJECT); hirqreg |= (CMOK|EHST); return; } /* TODO: Phantasy Star 2 throws this one. */ if (partitions[bufnum].numblks == 0) { printf("CD: buffer is already empty\n"); cr_standard_return(CD_STAT_REJECT); hirqreg |= (CMOK|EHST); return; } cd_getsectoroffsetnum(bufnum, §ofs, §num); for (i = sectofs; i < (sectofs + sectnum); i++) { partitions[bufnum].size -= partitions[bufnum].blocks[i]->size; cd_free_block(partitions[bufnum].blocks[i]); partitions[bufnum].blocks[i] = (blockT *)nullptr; partitions[bufnum].bnum[i] = 0xff; } cd_defragblocks(&partitions[bufnum]); partitions[bufnum].numblks -= sectnum; if (freeblocks == 200) { sectorstore = 0; } cd_stat &= ~CD_STAT_TRANS; cr_standard_return(cd_stat); hirqreg |= (CMOK|EHST); status_type = 0; } break; case 0x63: // get then delete sector data { uint32_t sectnum = cr4; uint32_t sectofs = cr2; uint32_t bufnum = cr3>>8; CDROM_LOG(("%s:CD: Get and delete sector data (SN %d SO %d BN %d)\n", machine().describe_context(), sectnum, sectofs, bufnum)) if (bufnum >= MAX_FILTERS) { printf("CD: invalid buffer number\n"); /* TODO: why this is happening? */ cr_standard_return(CD_STAT_REJECT); hirqreg |= (CMOK|EHST); return; } /* Yoshimoto Mahjong uses the REJECT status to verify when the data is ready. */ if (partitions[bufnum].numblks < sectnum) { printf("CD: buffer is not full %08x %08x\n",partitions[bufnum].numblks,sectnum); cr_standard_return(CD_STAT_REJECT); hirqreg |= (CMOK|EHST); return; } cd_getsectoroffsetnum(bufnum, §ofs, §num); xfertype32 = XFERTYPE32_GETDELETESECTOR; xferoffs = 0; xfersect = 0; xferdnum = 0; xfersectpos = sectofs; xfersectnum = sectnum; transpart = &partitions[bufnum]; cd_stat |= CD_STAT_TRANS; cr_standard_return(cd_stat); hirqreg |= (CMOK|EHST|DRDY); status_type = 0; } break; case 0x64: // put sector data /* After Burner 2, Out Run, Fantasy Zone and Dungeon Master Nexus trips this */ { uint32_t sectnum = cr4 & 0xff; uint32_t sectofs = cr2; uint32_t bufnum = cr3>>8; xfertype32 = XFERTYPE32_PUTSECTOR; /*TODO: eventual errors? */ cd_getsectoroffsetnum(bufnum, §ofs, §num); cd_stat |= CD_STAT_TRANS; xferoffs = 0; xfersect = 0; xferdnum = 0; xfersectpos = sectofs; xfersectnum = sectnum; transpart = &partitions[bufnum]; // allocate the blocks for (int i = xfersectpos; i < xfersectpos+xfersectnum; i++) { transpart->blocks[i] = cd_alloc_block(&transpart->bnum[i]); if(transpart->size == -1) transpart->size = 0; transpart->size += transpart->blocks[i]->size; transpart->numblks++; } } hirqreg |= (CMOK|DRDY); cr_standard_return(cd_stat); status_type = 0; break; case 0x65: popmessage("Move Sector data, contact MAMEdev"); hirqreg |= (CMOK); break; case 0x66: // copy sector data /* TODO: Sword & Sorcery / Riglord Saga 2 */ { uint32_t src_filter = (cr3>>8)&0xff; uint32_t dst_filter = cr1&0xff; uint32_t sectnum = cr4 & 0xff; //cd_stat |= CD_STAT_TRANS; //transpart = &partitions[dst_filter]; for (int i = 0; i < sectnum; i++) { // allocate the dst blocks partitions[dst_filter].blocks[i] = cd_alloc_block(&partitions[dst_filter].bnum[i]); if(partitions[dst_filter].size == -1) partitions[dst_filter].size = 0; partitions[dst_filter].size += partitions[dst_filter].blocks[i]->size; partitions[dst_filter].numblks++; //copy data for(int j = 0; j < sectlenin; j++) partitions[dst_filter].blocks[i]->data[j] = partitions[src_filter].blocks[i]->data[j]; //deallocate the src blocks //partitions[src_filter].size -= partitions[src_filter].blocks[i]->size; //cd_free_block(partitions[src_filter].blocks[i]); //partitions[src_filter].blocks[i] = (blockT *)nullptr; //partitions[src_filter].bnum[i] = 0xff; } } hirqreg |= (CMOK|ECPY); cr_standard_return(cd_stat); status_type = 0; break; case 0x67: // get copy error CDROM_LOG(("%s:CD: Get copy error\n", machine().describe_context())) logerror("Get copy error\n"); cr1 = cd_stat; cr2 = 0; cr3 = 0; cr4 = 0; hirqreg |= (CMOK); status_type = 0; break; case 0x70: // change directory CDROM_LOG(("%s:CD: Change Directory\n", machine().describe_context())) hirqreg |= (CMOK|EFLS); temp = (cr3&0xff)<<16; temp |= cr4; read_new_dir(temp); cr_standard_return(cd_stat); status_type = 0; break; case 0x71: // Read directory entry CDROM_LOG(("%s:CD: Read Directory Entry\n", machine().describe_context())) // uint32_t read_dir; // read_dir = ((cr3&0xff)<<16)|cr4; if((cr3 >> 8) < 0x24) cddevice = &filters[cr3 >> 8]; else cddevice = (filterT *)nullptr; /* TODO: */ //read_new_dir(read_dir - 2); cr_standard_return(cd_stat); hirqreg |= (CMOK|EFLS); status_type = 0; break; case 0x72: // Get file system scope CDROM_LOG(("CD: Get file system scope\n")) hirqreg |= (CMOK|EFLS); cr1 = cd_stat; cr2 = numfiles; // # of files in directory cr3 = 0x0100; // report directory held cr4 = firstfile; // first file id printf("%04x %04x %04x %04x\n",cr1,cr2,cr3,cr4); status_type = 0; break; case 0x73: // Get File Info CDROM_LOG(("%s:CD: Get File Info\n", machine().describe_context())) cd_stat |= CD_STAT_TRANS; cd_stat &= 0xff00; // clear top byte of return value playtype = 0; cdda_repeat_count = 0; hirqreg |= (CMOK|DRDY); temp = (cr3&0xff)<<16; temp |= cr4; if (temp == 0xffffff) // special { xfertype = XFERTYPE_FILEINFO_254; xfercount = 0; cr1 = cd_stat; cr2 = 0x5f4; cr3 = 0; cr4 = 0; } else { cr1 = cd_stat; cr2 = 6; // 6 words for single file // first 4 bytes = FAD address // second 4 bytes = length // last 4 bytes: // - unit size // - gap size // - file # // attributes flags cr3 = 0; cr4 = 0; printf("%08x %08x\n",curdir[temp].firstfad,curdir[temp].length); // first 4 bytes = FAD finfbuf[0] = (curdir[temp].firstfad>>24)&0xff; finfbuf[1] = (curdir[temp].firstfad>>16)&0xff; finfbuf[2] = (curdir[temp].firstfad>>8)&0xff; finfbuf[3] = (curdir[temp].firstfad&0xff); // second 4 bytes = length of file finfbuf[4] = (curdir[temp].length>>24)&0xff; finfbuf[5] = (curdir[temp].length>>16)&0xff; finfbuf[6] = (curdir[temp].length>>8)&0xff; finfbuf[7] = (curdir[temp].length&0xff); finfbuf[8] = curdir[temp].interleave_gap_size; finfbuf[9] = curdir[temp].file_unit_size; finfbuf[10] = temp; finfbuf[11] = curdir[temp].flags; xfertype = XFERTYPE_FILEINFO_1; xfercount = 0; } CDROM_LOG((" = %04x %04x %04x %04x %04x\n", hirqreg, cr1, cr2, cr3, cr4)) status_type = 0; break; case 0x74: // Read File CDROM_LOG(("%s:CD: Read File\n", machine().describe_context())) uint16_t file_offset,file_filter,file_id,file_size; file_offset = ((cr1 & 0xff)<<8)|(cr2 & 0xff); /* correct? */ file_filter = cr3 >> 8; file_id = ((cr3 & 0xff) << 16)|(cr4); file_size = ((curdir[file_id].length + sectlenin - 1) / sectlenin) - file_offset; cd_stat = CD_STAT_PLAY|0x80; // set "cd-rom" bit cd_curfad = (curdir[file_id].firstfad + file_offset); fadstoplay = file_size; if(file_filter < 0x24) cddevice = &filters[file_filter]; else cddevice = (filterT *)nullptr; printf("Read file %08x (%08x %08x) %02x %d\n",curdir[file_id].firstfad,cd_curfad,fadstoplay,file_filter,sectlenin); cr_standard_return(cd_stat); oddframe = 0; in_buffer = 0; playtype = 1; hirqreg |= (CMOK|EHST); status_type = 0; break; case 0x75: CDROM_LOG(("%s:CD: Abort File\n", machine().describe_context())) // bios expects "2bc" mask to work against this hirqreg |= (CMOK|EFLS); sectorstore = 0; xfertype32 = XFERTYPE32_INVALID; xferdnum = 0; if(((cd_stat & 0x0f00) != CD_STAT_NODISC) && ((cd_stat & 0x0f00) != CD_STAT_OPEN)) cd_stat = CD_STAT_PAUSE; // force to pause cr_standard_return(cd_stat); status_type = 0; break; case 0xe0: // appears to be copy protection check. needs only to return OK. CDROM_LOG(("%s:CD: Verify copy protection\n", machine().describe_context())) if(((cd_stat & 0x0f00) != CD_STAT_NODISC) && ((cd_stat & 0x0f00) != CD_STAT_OPEN)) cd_stat = CD_STAT_PAUSE; // cr1 = cd_stat; // necessary to pass // cr2 = 0x4; // hirqreg |= (CMOK|EFLS|CSCT); sectorstore = 1; hirqreg = 0xfc5; cr_standard_return(cd_stat); status_type = 0; break; case 0xe1: // get disc region CDROM_LOG(("%s:CD: Get disc region\n", machine().describe_context())) if(cd_stat != CD_STAT_NODISC && cd_stat != CD_STAT_OPEN) cd_stat = CD_STAT_PAUSE; cr1 = cd_stat; // necessary to pass cr2 = 0x4; // (must return this value to pass bios checks) cr3 = 0; cr4 = 0; hirqreg |= (CMOK); // cr_standard_return(cd_stat); status_type = 0; break; default: CDROM_LOG(("CD: Unknown command %04x\n", cr1>>8)) popmessage("CD Block unknown command %02x, contact MAMEdev",cr1>>8); hirqreg |= (CMOK); break; } if(status_type == 1) { prev_cr1 = cr1; prev_cr2 = cr2; prev_cr3 = cr3; prev_cr4 = cr4; } } TIMER_DEVICE_CALLBACK_MEMBER( saturn_state::stv_sh1_sim ) { if((cmd_pending == 0xf) && (!(hirqreg & CMOK))) cd_exec_command(); } TIMER_DEVICE_CALLBACK_MEMBER( saturn_state::stv_sector_cb ) { //sector_timer->reset(); //popmessage("%08x %08x %d %d",cd_curfad,fadstoplay,cmd_pending,cd_speed); cd_playdata(); if(cdrom_get_track_type(cdrom, cdrom_get_track(cdrom, cd_curfad)) == CD_TRACK_AUDIO) sector_timer->adjust(attotime::from_hz(75)); // 75 sectors / second = 150kBytes/second (cdda track ignores cd_speed setting) else sector_timer->adjust(attotime::from_hz(75*cd_speed)); // 75 / 150 sectors / second = 150 / 300kBytes/second /* TODO: doesn't boot if a disk isn't in? */ /* TODO: Check out when this really happens. (Daytona USA original version definitely wants it to be on).*/ //if(((cd_stat & 0x0f00) != CD_STAT_NODISC) && ((cd_stat & 0x0f00) != CD_STAT_OPEN)) hirqreg |= SCDQ; if(cd_stat & CD_STAT_PERI) { cr_standard_return(cd_stat); } } // global functions void saturn_state::stvcd_reset( void ) { int32_t i, j; hirqmask = 0xffff; hirqreg = 0xffff; cr1 = 'C'; cr2 = ('D'<<8) | 'B'; cr3 = ('L'<<8) | 'O'; cr4 = ('C'<<8) | 'K'; cd_stat = CD_STAT_PAUSE; cd_stat |= CD_STAT_PERI; cur_track = 0xff; curdir.clear(); xfertype = XFERTYPE_INVALID; xfertype32 = XFERTYPE32_INVALID; // reset flag vars buffull = sectorstore = 0; freeblocks = 200; sectlenin = sectlenout = 2048; lastbuf = 0xff; // reset buffer partitions for (i = 0; i < MAX_FILTERS; i++) { partitions[i].size = -1; partitions[i].numblks = 0; for (j = 0; j < MAX_BLOCKS; j++) { partitions[i].blocks[j] = (blockT *)nullptr; partitions[i].bnum[j] = 0xff; } } // reset blocks for (i = 0; i < MAX_BLOCKS; i++) { blocks[i].size = -1; memset(&blocks[i].data, 0, CD_MAX_SECTOR_DATA); } // open device if (cdrom) { cdrom_close(cdrom); cdrom = (cdrom_file *)nullptr; } cdrom_image_device *cddevice = machine().device("cdrom"); if (cddevice!=nullptr) { // MESS case cdrom = cddevice->get_cdrom_file(); } else { // MAME case cdrom = cdrom_open(machine().rom_load().get_disk_handle("cdrom")); } machine().device("cdda")->set_cdrom(cdrom); if (cdrom) { CDROM_LOG(("Opened CD-ROM successfully, reading root directory\n")) read_new_dir(0xffffff); // read root directory } else { cd_stat = CD_STAT_NODISC; } cd_speed = 2; cdda_repeat_count = 0; tray_is_closed = 1; sector_timer = machine().device("sector_timer"); sector_timer->adjust(attotime::from_hz(150)); // 150 sectors / second = 300kBytes/second sh1_timer = machine().device("sh1_cmd"); } saturn_state::blockT *saturn_state::cd_alloc_block(uint8_t *blknum) { int32_t i; // search the 200 available blocks for a free one for (i = 0; i < 200; i++) { if (blocks[i].size == -1) { freeblocks--; if (freeblocks <= 0) { buffull = 1; printf("buffull in cd_alloc_block\n"); } blocks[i].size = sectlenin; *blknum = i; CDROM_LOG(("CD: allocating block %d, size %x\n", i, sectlenin)) return &blocks[i]; } } buffull = 1; return (blockT *)nullptr; } void saturn_state::cd_free_block(blockT *blktofree) { int32_t i; CDROM_LOG(("cd_free_block: %x\n", (uint32_t)(uintptr_t)blktofree)) if(blktofree == nullptr) { return; } for (i = 0; i < 200; i++) { if (&blocks[i] == blktofree) { CDROM_LOG(("CD: freeing block %d\n", i)) } } blktofree->size = -1; freeblocks++; buffull = 0; hirqreg &= ~BFUL; } void saturn_state::cd_getsectoroffsetnum(uint32_t bufnum, uint32_t *sectoffs, uint32_t *sectnum) { if (*sectoffs == 0xffff) { // last sector printf("CD: Don't know how to handle offset ffff\n"); } else if (*sectnum == 0xffff) { *sectnum = partitions[bufnum].numblks - *sectoffs; } } void saturn_state::cd_defragblocks(partitionT *part) { uint32_t i, j; blockT *temp; uint8_t temp2; for (i = 0; i < (MAX_BLOCKS-1); i++) { for (j = i+1; j < MAX_BLOCKS; j++) { if ((part->blocks[i] == (blockT *)nullptr) && (part->blocks[j] != (blockT *)nullptr)) { temp = part->blocks[i]; part->blocks[i] = part->blocks[j]; part->blocks[j] = temp; temp2 = part->bnum[i]; part->bnum[i] = part->bnum[j]; part->bnum[j] = temp2; } } } } uint16_t saturn_state::cd_readWord(uint32_t addr) { uint16_t rv; switch (addr & 0xffff) { case 0x0008: // read HIRQ register case 0x000a: case 0x8008: case 0x800a: rv = hirqreg; rv &= ~DCHG; // always clear bit 6 (tray open) if (buffull) rv |= BFUL; else rv &= ~BFUL; if (sectorstore) rv |= CSCT; else rv &= ~CSCT; hirqreg = rv; // CDROM_LOG(("RW HIRQ: %04x\n", rv)) return rv; case 0x000c: case 0x000e: case 0x800c: case 0x800e: // CDROM_LOG(("RW HIRM: %04x\n", hirqmask)) printf("RW HIRM: %04x\n", hirqmask); return hirqmask; case 0x0018: case 0x001a: case 0x8018: case 0x801a: // CDROM_LOG(("RW CR1: %04x\n", cr1)) return cr1; case 0x001c: case 0x001e: case 0x801c: case 0x801e: // CDROM_LOG(("RW CR2: %04x\n", cr2)) return cr2; case 0x0020: case 0x0022: case 0x8020: case 0x8022: // CDROM_LOG(("RW CR3: %04x\n", cr3)) return cr3; case 0x0024: case 0x0026: case 0x8024: case 0x8026: // CDROM_LOG(("RW CR4: %04x\n", cr4)) //popmessage("%04x %04x %04x %04x",cr1,cr2,cr3,cr4); cmd_pending = 0; cd_stat |= CD_STAT_PERI; return cr4; case 0x8000: rv = 0xffff; switch (xfertype) { case XFERTYPE_TOC: rv = tocbuf[xfercount]<<8 | tocbuf[xfercount+1]; xfercount += 2; xferdnum += 2; if (xfercount > 102*4) { xfercount = 0; xfertype = XFERTYPE_INVALID; } break; case XFERTYPE_FILEINFO_1: rv = finfbuf[xfercount]<<8 | finfbuf[xfercount+1]; xfercount += 2; xferdnum += 2; if (xfercount > 6*2) { xfercount = 0; xfertype = XFERTYPE_INVALID; } break; case XFERTYPE_FILEINFO_254: // Lunar 2 if((xfercount % (6 * 2)) == 0) { uint32_t temp = 2 + (xfercount / (0x6 * 2)); // first 4 bytes = FAD finfbuf[0] = (curdir[temp].firstfad>>24)&0xff; finfbuf[1] = (curdir[temp].firstfad>>16)&0xff; finfbuf[2] = (curdir[temp].firstfad>>8)&0xff; finfbuf[3] = (curdir[temp].firstfad&0xff); // second 4 bytes = length of file finfbuf[4] = (curdir[temp].length>>24)&0xff; finfbuf[5] = (curdir[temp].length>>16)&0xff; finfbuf[6] = (curdir[temp].length>>8)&0xff; finfbuf[7] = (curdir[temp].length&0xff); finfbuf[8] = curdir[temp].interleave_gap_size; finfbuf[9] = curdir[temp].file_unit_size; finfbuf[10] = temp; finfbuf[11] = curdir[temp].flags; } rv = finfbuf[xfercount % (6 * 2)]<<8 | finfbuf[(xfercount % (6 * 2)) +1]; xfercount += 2; xferdnum += 2; if (xfercount > (254 * 6 * 2)) { xfercount = 0; xfertype = XFERTYPE_INVALID; } break; case XFERTYPE_SUBQ: rv = subqbuf[xfercount]<<8 | subqbuf[xfercount+1]; xfercount += 2; xferdnum += 2; if (xfercount > 5*2) { xfercount = 0; xfertype = XFERTYPE_INVALID; } break; case XFERTYPE_SUBRW: rv = subrwbuf[xfercount]<<8 | subrwbuf[xfercount+1]; xfercount += 2; xferdnum += 2; if (xfercount > 12*2) { xfercount = 0; xfertype = XFERTYPE_INVALID; } break; default: printf("STVCD: Unhandled xfer type %d\n", (int)xfertype); rv = 0; break; } return rv; default: CDROM_LOG(("CD: RW %08x\n", addr)) return 0xffff; } } uint32_t saturn_state::cd_readLong(uint32_t addr) { uint32_t rv = 0; switch (addr & 0xffff) { case 0x8000: switch (xfertype32) { case XFERTYPE32_GETSECTOR: case XFERTYPE32_GETDELETESECTOR: // make sure we have sectors left if (xfersect < xfersectnum) { // get next longword rv = (transpart->blocks[xfersectpos+xfersect]->data[xferoffs + 0]<<24) | (transpart->blocks[xfersectpos+xfersect]->data[xferoffs + 1]<<16) | (transpart->blocks[xfersectpos+xfersect]->data[xferoffs + 2]<<8) | (transpart->blocks[xfersectpos+xfersect]->data[xferoffs + 3]<<0); xferdnum += 4; xferoffs += 4; // did we run out of sector? if (xferoffs >= transpart->blocks[xfersect]->size) { CDROM_LOG(("CD: finished xfer of block %d of %d\n", xfersect+1, xfersectnum)) xferoffs = 0; xfersect++; } } else // sectors are done, kill 'em all if we can { if (xfertype32 == XFERTYPE32_GETDELETESECTOR) { int32_t i; CDROM_LOG(("Killing sectors in done\n")) // deallocate the blocks for (i = xfersectpos; i < xfersectpos+xfersectnum; i++) { cd_free_block(transpart->blocks[i]); transpart->blocks[i] = (blockT *)nullptr; transpart->bnum[i] = 0xff; } // defrag what's left cd_defragblocks(transpart); // clean up our state transpart->size -= xferdnum; transpart->numblks -= xfersectnum; /* TODO: is this correct? */ xfertype32 = XFERTYPE32_INVALID; } } break; default: printf("CD: unhandled 32-bit transfer type\n"); break; } return rv; default: CDROM_LOG(("CD: RL %08x\n", addr)) return 0xffff; } } void saturn_state::cd_writeLong(uint32_t addr, uint32_t data) { switch (addr & 0xffff) { case 0x8000: switch (xfertype32) { case XFERTYPE32_PUTSECTOR: // make sure we have sectors left if (xfersect < xfersectnum) { // get next longword transpart->blocks[xfersectpos+xfersect]->data[xferoffs + 0] = (data >> 24) & 0xff; transpart->blocks[xfersectpos+xfersect]->data[xferoffs + 1] = (data >> 16) & 0xff; transpart->blocks[xfersectpos+xfersect]->data[xferoffs + 2] = (data >> 8) & 0xff; transpart->blocks[xfersectpos+xfersect]->data[xferoffs + 3] = (data >> 0) & 0xff; xferdnum += 4; xferoffs += 4; // did we run out of sector? if (xferoffs >= transpart->blocks[xfersectpos+xfersect]->size) { CDROM_LOG(("CD: finished xfer of block %d of %d\n", xfersect+1, xfersectnum)) xferoffs = 0; xfersect++; } } else // sectors are done { /* Virtual On doesnt want this to be resetted. */ //xfertype32 = XFERTYPE32_INVALID; } break; default: printf("CD: unhandled 32-bit transfer type write\n"); break; } break; default: break; } } void saturn_state::cd_writeWord(uint32_t addr, uint16_t data) { switch(addr & 0xffff) { case 0x0008: case 0x000a: case 0x8008: case 0x800a: // CDROM_LOG(("%s:WW HIRQ: %04x & %04x => %04x\n", machine().describe_context(), hirqreg, data, hirqreg & data)) hirqreg &= data; return; case 0x000c: case 0x000e: case 0x800c: case 0x800e: // CDROM_LOG(("WW HIRM: %04x => %04x\n", hirqmask, data)) printf("WW HIRM: %04x => %04x\n", hirqmask, data); hirqmask = data; return; case 0x0018: case 0x001a: case 0x8018: case 0x801a: // CDROM_LOG(("WW CR1: %04x\n", data)) cr1 = data; cd_stat &= ~CD_STAT_PERI; cmd_pending |= 1; sh1_timer->adjust(attotime::never); break; case 0x001c: case 0x001e: case 0x801c: case 0x801e: // CDROM_LOG(("WW CR2: %04x\n", data)) cr2 = data; cmd_pending |= 2; break; case 0x0020: case 0x0022: case 0x8020: case 0x8022: // CDROM_LOG(("WW CR3: %04x\n", data)) cr3 = data; cmd_pending |= 4; break; case 0x0024: case 0x0026: case 0x8024: case 0x8026: // CDROM_LOG(("WW CR4: %04x\n", data)) cr4 = data; cmd_pending |= 8; sh1_timer->adjust(attotime::from_hz(get_timing_command())); break; default: CDROM_LOG(("CD: WW %08x %04x\n", addr, data)) break; } } READ32_MEMBER( saturn_state::stvcd_r ) { uint32_t rv = 0; offset <<= 2; switch (offset) { case 0x88008: case 0x8800a: case 0x8800c: case 0x8800e: case 0x88018: case 0x8801a: case 0x8801c: case 0x8801e: case 0x88020: case 0x88022: case 0x88024: case 0x88026: case 0x90008: case 0x9000a: case 0x9000c: case 0x9000e: case 0x90018: case 0x9001a: case 0x9001c: case 0x9001e: case 0x90020: case 0x90022: case 0x90024: case 0x90026: rv = cd_readWord(offset); return rv<<16; case 0x98000: case 0x18000: if (mem_mask == 0xffffffff) { rv = cd_readLong(offset); } else if (mem_mask == 0xffff0000) { rv = cd_readWord(offset)<<16; } else if (mem_mask == 0x0000ffff) { rv = cd_readWord(offset); } else { osd_printf_error("CD: Unknown data buffer read @ mask = %08x\n", mem_mask); } break; default: printf("Unknown CD read %x\n", offset); break; } return rv; } WRITE32_MEMBER( saturn_state::stvcd_w ) { offset <<= 2; switch (offset) { case 0x18000: if (mem_mask == 0xffffffff) cd_writeLong(offset, data); else printf("CD: Unknown data buffer write @ mask = %08x\n", mem_mask); break; case 0x88008: case 0x8800a: case 0x8800c: case 0x8800e: case 0x88018: case 0x8801a: case 0x8801c: case 0x8801e: case 0x88020: case 0x88022: case 0x88024: case 0x88026: case 0x90008: case 0x9000a: case 0x9000c: case 0x9000e: case 0x90018: case 0x9001a: case 0x9001c: case 0x9001e: case 0x90020: case 0x90022: case 0x90024: case 0x90026: cd_writeWord(offset, data>>16); break; default: printf("Unknown CD write %x @ %x\n", data, offset); //xferdnum = 0x8c00; break; } } // iso9660 parsing void saturn_state::read_new_dir(uint32_t fileno) { int foundpd, i; uint32_t cfad;//, dirfad; uint8_t sect[2048]; if (fileno == 0xffffff) { cfad = 166; // first sector of directory as per iso9660 specs foundpd = 0; // search for primary vol. desc while ((!foundpd) && (cfad < 200)) { if(sectlenin != 2048) popmessage("Sector Length %d, contact MAMEdev (0)",sectlenin); memset(sect, 0, 2048); cd_readblock(cfad++, sect); if ((sect[1] == 'C') && (sect[2] == 'D') && (sect[3] == '0') && (sect[4] == '0') && (sect[5] == '1')) { switch (sect[0]) { case 0: // boot record break; case 1: // primary vol. desc foundpd = 1; break; case 2: // secondary vol desc break; case 3: // vol. section descriptor break; case 0xff: cfad = 200; break; } } } // got primary vol. desc. if (foundpd) { //dirfad = sect[140] | (sect[141]<<8) | (sect[142]<<16) | (sect[143]<<24); //dirfad += 150; // parse root entry curroot.firstfad = sect[158] | (sect[159]<<8) | (sect[160]<<16) | (sect[161]<<24); curroot.firstfad += 150; curroot.length = sect[166] | (sect[167]<<8) | (sect[168]<<16) | (sect[169]<<24); curroot.flags = sect[181]; for (i = 0; i < sect[188]; i++) { curroot.name[i] = sect[189+i]; } curroot.name[i] = '\0'; // terminate // easy to fix, but make sure we *need* to first if (curroot.length > MAX_DIR_SIZE) { osd_printf_error("ERROR: root directory too big (%d)\n", curroot.length); } // done with all that, read the root directory now make_dir_current(curroot.firstfad); } } else { if (curdir[fileno].length > MAX_DIR_SIZE) { osd_printf_error("ERROR: new directory too big (%d)!\n", curdir[fileno].length); } make_dir_current(curdir[fileno].firstfad); } } // makes the directory pointed to by FAD current void saturn_state::make_dir_current(uint32_t fad) { int i; uint32_t nextent, numentries; std::vector sect(MAX_DIR_SIZE); direntryT *curentry; memset(§[0], 0, MAX_DIR_SIZE); if(sectlenin != 2048) popmessage("Sector Length %d, contact MAMEdev (1)",sectlenin); for (i = 0; i < (curroot.length/2048); i++) { cd_readblock(fad+i, §[2048*i]); } nextent = 0; numentries = 0; while (nextent < MAX_DIR_SIZE) { if (sect[nextent]) { nextent += sect[nextent]; numentries++; } else { nextent = MAX_DIR_SIZE; } } curdir.resize(numentries); curentry = &curdir[0]; numfiles = numentries; nextent = 0; while (numentries) { // [0] record size // [1] xa record size // [2-5] lba // [6-9] (lba?) // [10-13] size // [14-17] (size?) // [18] year // [19] month // [20] day // [21] hour // [22] minute // [23] second // [24] gmt offset // [25] flags // [26] file unit size // [27] interleave gap size // [28-29] volume sequencer number // [30-31] (volume sequencer number?) // [32] name character size // [33+ ...] file name curentry->record_size = sect[nextent+0]; curentry->xa_record_size = sect[nextent+1]; curentry->firstfad = sect[nextent+2] | (sect[nextent+3]<<8) | (sect[nextent+4]<<16) | (sect[nextent+5]<<24); curentry->firstfad += 150; curentry->length = sect[nextent+10] | (sect[nextent+11]<<8) | (sect[nextent+12]<<16) | (sect[nextent+13]<<24); curentry->year = sect[nextent+18]; curentry->month = sect[nextent+19]; curentry->day = sect[nextent+20]; curentry->hour = sect[nextent+21]; curentry->minute = sect[nextent+22]; curentry->second = sect[nextent+23]; curentry->gmt_offset = sect[nextent+24]; curentry->flags = sect[nextent+25]; curentry->file_unit_size = sect[nextent+26]; curentry->interleave_gap_size = sect[nextent+27]; curentry->volume_sequencer_number = sect[nextent+28] | (sect[nextent+29] << 8); for (i = 0; i < sect[nextent+32]; i++) { curentry->name[i] = sect[nextent+33+i]; } curentry->name[i] = '\0'; // terminate //printf("%08x %08x %s %d/%d/%d\n",curentry->firstfad,curentry->length,curentry->name,curentry->year,curentry->month,curentry->day); nextent += sect[nextent]; curentry++; numentries--; } for (i = 0; i < numfiles; i++) { if (!(curdir[i].flags & 0x02)) { firstfile = i; i = numfiles; } } } void saturn_state::stvcd_exit( void ) { curdir.clear(); if (cdrom) { cdrom_image_device *cddevice = machine().device("cdrom"); if (cddevice==nullptr) { cdrom_close(cdrom); } cdrom = (cdrom_file *)nullptr; } } void saturn_state::cd_readTOC(void) { int i, ntrks, tocptr, fad; xfertype = XFERTYPE_TOC; xfercount = 0; if (cdrom) { ntrks = cdrom_get_last_track(cdrom); } else { ntrks = 0; } // data format for Saturn TOC: // no header. // 4 bytes per track // top nibble of first byte is CTRL info // low nibble is ADR // next 3 bytes are FAD address (LBA + 150) // there are always 99 track entries (0-98) // unused tracks are ffffffff. // entries 99-101 are metadata tocptr = 0; // starting point of toc entries for (i = 0; i < ntrks; i++) { if (cdrom) { //tocbuf[tocptr] = sega_cdrom_get_adr_control(cdrom, i); //HACK: ddsom does not enter ingame with the line above! tocbuf[tocptr] = cdrom_get_adr_control(cdrom, i)<<4 | 0x01; } else { tocbuf[tocptr] = 0xff; } if (cdrom) { fad = cdrom_get_track_start(cdrom, i) + 150; tocbuf[tocptr+1] = (fad>>16)&0xff; tocbuf[tocptr+2] = (fad>>8)&0xff; tocbuf[tocptr+3] = fad&0xff; } else { tocbuf[tocptr+1] = 0xff; tocbuf[tocptr+2] = 0xff; tocbuf[tocptr+3] = 0xff; } tocptr += 4; } // fill in the rest for ( ; i < 99; i++) { tocbuf[tocptr] = 0xff; tocbuf[tocptr+1] = 0xff; tocbuf[tocptr+2] = 0xff; tocbuf[tocptr+3] = 0xff; tocptr += 4; } // tracks 99-101 are special metadata // $$$FIXME: what to do with the address info for these? tocptr = 99 * 4; tocbuf[tocptr] = tocbuf[0]; // get ctrl/adr from first track tocbuf[tocptr+1] = 1; // first track's track # tocbuf[tocptr+2] = 0; tocbuf[tocptr+3] = 0; tocbuf[tocptr+4] = tocbuf[(ntrks-1)*4]; // ditto for last track tocbuf[tocptr+5] = ntrks; // last track's track # tocbuf[tocptr+6] = 0; tocbuf[tocptr+7] = 0; // get total disc length (start of lead-out) fad = cdrom_get_track_start(cdrom, 0xaa) + 150; tocbuf[tocptr+8] = tocbuf[0]; tocbuf[tocptr+9] = (fad>>16)&0xff; tocbuf[tocptr+10] = (fad>>8)&0xff; tocbuf[tocptr+11] = fad&0xff; } saturn_state::partitionT *saturn_state::cd_filterdata(filterT *flt, int trktype, uint8_t *p_ok) { int match, keepgoing; partitionT *filterprt; CDROM_LOG(("cd_filterdata, trktype %d\n", trktype)) match = 1; keepgoing = 2; lastbuf = flt->condtrue; // loop on the filters do { // FAD range check? /* according to an obscure document note, this switches the filter connector to be false if the range fails ... I think ... */ if (flt->mode & 0x40) { if ((cd_curfad < flt->fad) || (cd_curfad > (flt->fad + flt->range))) { printf("curfad reject %08x %08x %08x %08x\n",cd_curfad,fadstoplay,flt->fad,flt->fad+flt->range); match = 0; //lastbuf = flt->condfalse; //flt = &filters[lastbuf]; } } if ((trktype != CD_TRACK_AUDIO) && (curblock.data[15] == 2)) { if (flt->mode & 1) // file number { if (curblock.fnum != flt->fid) { printf("fnum reject\n"); match = 0; } } if (flt->mode & 2) // channel number { if (curblock.chan != flt->chan) { printf("channel number reject\n"); match = 0; } } if (flt->mode & 4) // sub mode { if((curblock.subm & flt->smmask) != flt->smval) { printf("sub mode reject\n"); match = 0; } } if (flt->mode & 8) // coding information { if((curblock.cinf & flt->cimask) != flt->cival) { printf("coding information reject\n"); match = 0; } } if (flt->mode & 0x10) // reverse subheader conditions { match ^= 1; } } if (match) { //lastbuf = flt->condtrue; //filterprt = &partitions[lastbuf]; // we're done keepgoing = 0; } else { lastbuf = flt->condfalse; // reject sector if no match on either connector if ((lastbuf == 0xff) || (keepgoing == 0)) { *p_ok = 0; return (partitionT *)nullptr; } // try again using the filter that was on the "false" connector flt = &filters[lastbuf]; match = 1; // and exit if we fail keepgoing--; } } while (keepgoing); filterprt = &partitions[lastbuf]; // try to allocate a block filterprt->blocks[filterprt->numblks] = cd_alloc_block(&filterprt->bnum[filterprt->numblks]); // did the allocation succeed? if (filterprt->blocks[filterprt->numblks] == (blockT *)nullptr) { *p_ok = 0; return (partitionT *)nullptr; } // copy working block to the newly allocated one memcpy(filterprt->blocks[filterprt->numblks], &curblock, sizeof(blockT)); // and massage the data format a bit switch (curblock.size) { case 2048: // user data if (curblock.data[15] == 2) { // mode 2 memcpy(&filterprt->blocks[filterprt->numblks]->data[0], &curblock.data[24], curblock.size); } else { // mode 1 memcpy(&filterprt->blocks[filterprt->numblks]->data[0], &curblock.data[16], curblock.size); } break; case 2324: // Mode 2 Form 2 data memcpy(&filterprt->blocks[filterprt->numblks]->data[0], &curblock.data[24], curblock.size); break; case 2336: // Mode 2 Form 2 skip sync/header memcpy(&filterprt->blocks[filterprt->numblks]->data[0], &curblock.data[16], curblock.size); break; case 2340: // Mode 2 Form 2 skip sync only memcpy(&filterprt->blocks[filterprt->numblks]->data[0], &curblock.data[12], curblock.size); break; case 2352: // want all data, it's already done, so don't do it again :) break; } // update the status of the partition if (filterprt->size == -1) filterprt->size = 0; filterprt->size += filterprt->blocks[filterprt->numblks]->size; filterprt->numblks++; *p_ok = 1; return filterprt; } // read a single sector off the CD, applying the current filter(s) as necessary saturn_state::partitionT *saturn_state::cd_read_filtered_sector(int32_t fad, uint8_t *p_ok) { int trktype; if ((cddevice != nullptr) && (!buffull)) { // find out the track's type trktype = cdrom_get_track_type(cdrom, cdrom_get_track(cdrom, fad-150)); // now get a raw 2352 byte sector - if it's mode 1, get mode1_raw if ((trktype == CD_TRACK_MODE1) || (trktype == CD_TRACK_MODE1_RAW)) { cdrom_read_data(cdrom, fad-150, curblock.data, CD_TRACK_MODE1_RAW); } else if (trktype != CD_TRACK_AUDIO) // if not audio it must be mode 2 so get mode2_raw { cdrom_read_data(cdrom, fad-150, curblock.data, CD_TRACK_MODE2_RAW); } else { cdrom_read_data(cdrom, fad-150, curblock.data, CD_TRACK_AUDIO); } curblock.size = sectlenin; curblock.FAD = fad; // if track is Mode 2, get the subheader values if ((trktype != CD_TRACK_AUDIO) && (curblock.data[15] == 2)) { curblock.chan = curblock.data[17]; curblock.fnum = curblock.data[16]; curblock.subm = curblock.data[18]; curblock.cinf = curblock.data[19]; // if it's Form 2, the length is actually 2324 bytes if (curblock.subm & 0x20) { curblock.size = 2324; } } return cd_filterdata(cddevice, trktype, &*p_ok); } *p_ok = 0; return (partitionT *)nullptr; } // loads in data set up by a CD-block PLAY command void saturn_state::cd_playdata( void ) { if ((cd_stat & 0x0f00) == CD_STAT_SEEK) { int32_t fad_diff; //printf("PRE %08x %08x %08x %d\n",cd_curfad,cd_fad_seek,cd_stat,cd_fad_seek - cd_curfad); fad_diff = (cd_fad_seek - cd_curfad); /* Zero Divide wants this TODO: timings. */ if(fad_diff > (750*cd_speed)) { //printf("PRE FFWD %08x %08x %08x %d %d\n",cd_curfad,cd_fad_seek,cd_stat,cd_fad_seek - cd_curfad,750*cd_speed); cd_curfad += (750*cd_speed); //printf("POST FFWD %08x %08x %08x %d %d\n",cd_curfad,cd_fad_seek,cd_stat,cd_fad_seek - cd_curfad, 750*cd_speed); } else if(fad_diff < (-750*cd_speed)) { //printf("PRE REW %08x %08x %08x %d %d\n",cd_curfad,cd_fad_seek,cd_stat,cd_fad_seek - cd_curfad, -750*cd_speed); cd_curfad -= (750*cd_speed); //printf("POST REW %08x %08x %08x %d %d\n",cd_curfad,cd_fad_seek,cd_stat,cd_fad_seek - cd_curfad, -750*cd_speed); } else { //printf("Ready\n"); cd_curfad = cd_fad_seek; cd_stat = CD_STAT_PLAY; } return; } if ((cd_stat & 0x0f00) == CD_STAT_PLAY) { if (fadstoplay) { logerror("STVCD: Reading FAD %d\n", cd_curfad); if (cdrom) { uint8_t p_ok; if(cdrom_get_track_type(cdrom, cdrom_get_track(cdrom, cd_curfad)) != CD_TRACK_AUDIO) { cd_read_filtered_sector(cd_curfad,&p_ok); machine().device("cdda")->stop_audio(); //stop any pending CD-DA } else { p_ok = 1; // TODO machine().device("cdda")->start_audio(cd_curfad, 1); } if(p_ok) { cd_curfad++; fadstoplay--; hirqreg |= CSCT; sectorstore = 1; if (!fadstoplay) { if(cdda_repeat_count >= cdda_maxrepeat) { CDROM_LOG(("cd_playdata: playback ended\n")) cd_stat = CD_STAT_PAUSE; hirqreg |= PEND; if (playtype == 1) { CDROM_LOG(("cd_playdata: setting EFLS\n")) hirqreg |= EFLS; } } else { if(cdda_repeat_count < 0xe) cdda_repeat_count++; cd_curfad = cdrom_get_track_start(cdrom, cur_track-1) + 150; fadstoplay = cdrom_get_track_start(cdrom, cur_track) - cd_curfad; } } } } } } } // loads a single sector off the CD, anywhere from FAD 150 on up void saturn_state::cd_readblock(uint32_t fad, uint8_t *dat) { if (cdrom) { cdrom_read_data(cdrom, fad-150, dat, CD_TRACK_MODE1); } } void saturn_state::stvcd_set_tray_open( void ) { if(!tray_is_closed) return; hirqreg |= DCHG; cd_stat = CD_STAT_OPEN; cdrom = (cdrom_file *)nullptr; tray_is_closed = 0; popmessage("Tray Open"); } void saturn_state::stvcd_set_tray_close( void ) { /* avoid user attempts to load a CD-ROM without opening the tray first (emulation asserts anyway with current framework) */ if(tray_is_closed) return; hirqreg |= DCHG; cdrom_image_device *cddevice = machine().device("cdrom"); if (cddevice!=nullptr) { // MESS case cdrom = cddevice->get_cdrom_file(); } else { // MAME case cdrom = cdrom_open(machine().rom_load().get_disk_handle("cdrom")); } machine().device("cdda")->set_cdrom(cdrom); if (cdrom) { CDROM_LOG(("Opened CD-ROM successfully, reading root directory\n")) //read_new_dir(0xffffff); // read root directory cd_stat = CD_STAT_PAUSE; } else { cd_stat = CD_STAT_NODISC; } cd_speed = 2; cdda_repeat_count = 0; tray_is_closed = 1; popmessage("Tray Close"); }