From b79e95ee97915f3fe1ddee9e6a5dccf38ce3137e Mon Sep 17 00:00:00 2001 From: Andre Zeps Date: Mon, 16 Dec 2024 06:04:40 +0100 Subject: [PATCH] Philips CD-i (#939) * CD-i: add cd reading support Inspired by the PSX code * CD-i: add cd reading support Fix CDI track type * CD-i: NvRAM backup and restore --- menu.cpp | 8 +- support.h | 3 + support/cdi/cdi.cpp | 482 ++++++++++++++++++++++++++++++++++++++++++++ support/cdi/cdi.h | 11 + user_io.cpp | 36 +++- user_io.h | 1 + 6 files changed, 535 insertions(+), 6 deletions(-) create mode 100644 support/cdi/cdi.cpp create mode 100644 support/cdi/cdi.h diff --git a/menu.cpp b/menu.cpp index 4b5f7a5..f70ca53 100644 --- a/menu.cpp +++ b/menu.cpp @@ -2227,7 +2227,7 @@ void HandleUI(void) if (is_x86() || is_pcxt()) strcpy(Selected_tmp, x86_get_image_path(ioctl_index)); if (is_psx() && (ioctl_index == 2 || ioctl_index == 3)) fs_Options |= SCANO_SAVES; - if (is_saturn() || is_pce() || is_megacd() || is_x86() || (is_psx() && !(fs_Options & SCANO_SAVES)) || is_neogeo()) + if (is_saturn() || is_pce() || is_megacd() || is_x86() || is_cdi() || (is_psx() && !(fs_Options & SCANO_SAVES)) || is_neogeo()) { //look for CHD too if (!strcasestr(ext, "CHD")) @@ -2493,6 +2493,10 @@ void HandleUI(void) psx_mount_cd(user_io_ext_idx(selPath, fs_pFileExt) << 6 | (menusub + 1), ioctl_index, selPath); cheats_init(selPath, 0); } + else if (is_cdi()) + { + cdi_mount_cd(ioctl_index, selPath); + } else if (is_saturn()) { saturn_set_image(ioctl_index, selPath); @@ -7301,7 +7305,7 @@ int menu_allow_cfg_switch() void menu_process_save() { - menu_save_timer = GetTimer(1000); + menu_save_timer = GetTimer(500); } static char pchar[] = { 0x8C, 0x8E, 0x8F, 0x90, 0x91, 0x7F }; diff --git a/support.h b/support.h index e1055f9..c0685ba 100644 --- a/support.h +++ b/support.h @@ -43,6 +43,9 @@ // PSX support #include "support/psx/psx.h" +// CD-i support +#include "support/cdi/cdi.h" + // UEF support #include "support/uef/uef_reader.h" diff --git a/support/cdi/cdi.cpp b/support/cdi/cdi.cpp new file mode 100644 index 0000000..21035ef --- /dev/null +++ b/support/cdi/cdi.cpp @@ -0,0 +1,482 @@ + +#include +#include +#include +#include + +#include "../../file_io.h" +#include "../../user_io.h" +#include "../../spi.h" +#include "../../hardware.h" +#include "../../menu.h" +#include "cdi.h" +#include "../../cd.h" +#include "../chd/mister_chd.h" +#include + +static char buf[1024]; +#define CD_SECTOR_LEN 2352 + +static uint8_t *chd_hunkbuf = NULL; +static int chd_hunknum; + +static int sgets(char *out, int sz, char **in) +{ + *out = 0; + do + { + char *instr = *in; + int cnt = 0; + + while (*instr && *instr != 10) + { + if (*instr == 13) + { + instr++; + continue; + } + + if (cnt < sz - 1) + { + out[cnt++] = *instr; + out[cnt] = 0; + } + + instr++; + } + + if (*instr == 10) + instr++; + *in = instr; + } while (!*out && **in); + + return *out; +} + +static void unload_chd(toc_t *table) +{ + if (table->chd_f) + { + chd_close(table->chd_f); + } + if (chd_hunkbuf) + free(chd_hunkbuf); + memset(table, 0, sizeof(toc_t)); + chd_hunknum = -1; +} + +static void unload_cue(toc_t *table) +{ + for (int i = 0; i < table->last; i++) + { + FileClose(&table->tracks[i].f); + } + memset(table, 0, sizeof(toc_t)); +} + +static int load_chd(const char *filename, toc_t *table) +{ + + unload_chd(table); + chd_error err = mister_load_chd(filename, table); + if (err != CHDERR_NONE) + { + return 0; + } + + /* CD-i core expects the TOC values for track start/end to not take into account + * pregap, unlike some other cores. Adjust the CHD toc to reflect this + */ + + for (int i = 0; i < table->last; i++) + { + if (i == 0) // First track fakes a pregap even if it doesn't exist + { + table->tracks[i].indexes[1] = 150; + table->tracks[i].start = 150; + table->tracks[i].end += 150 - 1; + } + else + { + int frame_cnt = table->tracks[i].end - table->tracks[i].start; + frame_cnt += table->tracks[i].indexes[1]; + table->tracks[i].start = table->tracks[i - 1].end + 1; + table->tracks[i].end = table->tracks[i].start + frame_cnt - 1; + } + } + + table->end = table->tracks[table->last - 1].end + 1; + + chd_hunkbuf = (uint8_t *)malloc(table->chd_hunksize); + chd_hunknum = -1; + + return 1; +} + +static int load_cue(const char *filename, toc_t *table) +{ + static char fname[1024 + 10]; + static char line[128]; + char *ptr, *lptr; + static char toc[100 * 1024]; + + unload_cue(table); + printf("\x1b[32mCDI: Open CUE: %s\n\x1b[0m", fname); + + strcpy(fname, filename); + + memset(toc, 0, sizeof(toc)); + if (!FileLoad(fname, toc, sizeof(toc) - 1)) + { + printf("\x1b[32mCDI: cannot load file: %s\n\x1b[0m", fname); + return 0; + } + + int mm, ss, bb; + int pregap = 0; + + char *buf = toc; + while (sgets(line, sizeof(line), &buf)) + { + lptr = line; + while (*lptr == 0x20) + lptr++; + + /* decode FILE commands */ + if (!(memcmp(lptr, "FILE", 4))) + { + ptr = fname + strlen(fname) - 1; + while ((ptr - fname) && (*ptr != '/') && (*ptr != '\\')) + ptr--; + if (ptr - fname) + ptr++; + + lptr += 4; + while (*lptr == 0x20) + lptr++; + + if (*lptr == '\"') + { + lptr++; + while ((*lptr != '\"') && (lptr <= (line + 128)) && (ptr < (fname + 1023))) + *ptr++ = *lptr++; + } + else + { + while ((*lptr != 0x20) && (lptr <= (line + 128)) && (ptr < (fname + 1023))) + *ptr++ = *lptr++; + } + *ptr = 0; + + if (!FileOpen(&table->tracks[table->last].f, fname)) + return 0; + + printf("\x1b[32mCDI: Open track file: %s\n\x1b[0m", fname); + + table->tracks[table->last].offset = 0; + + if (!strstr(lptr, "BINARY")) + { + FileClose(&table->tracks[table->last].f); + printf("\x1b[32mCDI: unsupported file: %s\n\x1b[0m", fname); + return 0; + } + } + + /* decode PREGAP commands */ + else if (sscanf(lptr, "PREGAP %02d:%02d:%02d", &mm, &ss, &bb) == 3) + { + // Single bin specific, add pregab but subtract inherent pregap + pregap += bb + ss * 75 + mm * 60 * 75; + table->tracks[table->last].pregap = 1; + } + /* decode TRACK commands */ + else if ((sscanf(lptr, "TRACK %02d %*s", &bb)) || (sscanf(lptr, "TRACK %d %*s", &bb))) + { + pregap = 0; + if (bb != (table->last + 1)) + { + FileClose(&table->tracks[table->last].f); + printf("\x1b[32mCDI: missing tracks: %s\n\x1b[0m", fname); + return 0; + } + + if (strstr(lptr, "MODE1/2352") || strstr(lptr, "MODE2/2352") || strstr(lptr, "CDI/2352")) + { + table->tracks[table->last].sector_size = CD_SECTOR_LEN; + table->tracks[table->last].type = 1; + if (!table->last) + table->end = 150; // implicit 2 seconds pregap for track 1 + } + else if (strstr(lptr, "AUDIO")) + { + table->tracks[table->last].sector_size = CD_SECTOR_LEN; + table->tracks[table->last].type = 0; + } + else + { + FileClose(&table->tracks[table->last].f); + printf("\x1b[32mCDI: unsupported track type: %s\n\x1b[0m", lptr); + return 0; + } + } + + /* decode INDEX commands */ + else if ((sscanf(lptr, "INDEX 00 %02d:%02d:%02d", &mm, &ss, &bb) == 3) || + (sscanf(lptr, "INDEX 0 %02d:%02d:%02d", &mm, &ss, &bb) == 3)) + { + // Single bin specific + if (!table->tracks[table->last].f.opened()) + { + + pregap = bb + ss * 75 + mm * 60 * 75; + } + } + else if ((sscanf(lptr, "INDEX 01 %02d:%02d:%02d", &mm, &ss, &bb) == 3) || + (sscanf(lptr, "INDEX 1 %02d:%02d:%02d", &mm, &ss, &bb) == 3)) + { + if (!table->tracks[table->last].f.opened()) + { + table->tracks[table->last].start = bb + ss * 75 + mm * 60 * 75; + if (table->tracks[table->last].pregap) + table->tracks[table->last].start += pregap; + // Subtract the fake 150 sector pregap used for the first data track + table->tracks[table->last].offset = table->tracks[table->last].start * table->tracks[table->last].sector_size; + if (table->last) + { + table->tracks[table->last - 1].end = table->tracks[table->last].start - 1; + if (pregap) + { + table->tracks[table->last].indexes[1] = table->tracks[table->last].start - pregap; + if (!table->tracks[table->last].pregap) + { + table->tracks[table->last].offset -= CD_SECTOR_LEN * table->tracks[table->last].indexes[1]; + table->tracks[table->last].indexes[1] = table->tracks[table->last].start - pregap; + } + else + { + table->tracks[table->last].indexes[1] = pregap; + } + } + } + else if (table->tracks[table->last].type) + { + table->tracks[table->last].indexes[1] = 150; + } + } + else + { + table->tracks[table->last].indexes[1] = bb + ss * 75 + mm * 60 * 75; + if (table->tracks[table->last].type && !table->last) + table->tracks[table->last].indexes[1] = 150; + table->tracks[table->last].start = table->end; + table->end += (table->tracks[table->last].f.size / table->tracks[table->last].sector_size); + table->tracks[table->last].offset = 0; + } + table->tracks[table->last].end = table->end - 1; + table->last++; + if (table->last >= 99) + break; + } + } + + for (int i = 0; i < table->last; i++) + { + printf("\x1b[32mCDI: Track = %u, start = %u, end = %u, offset = %d, sector_size=%d, type = %u\n\x1b[0m", i, table->tracks[i].start, table->tracks[i].end, table->tracks[i].offset, table->tracks[i].sector_size, table->tracks[i].type); + if (table->tracks[i].indexes[1]) + printf("\x1b[32mCDI: Track = %u,Index1 = %u seconds\n\x1b[0m", i, table->tracks[i].indexes[1] / 75); + } + + return 1; +} + +static int load_cd_image(const char *filename, toc_t *table) +{ + + const char *ext = strrchr(filename, '.'); + if (!ext) + return 0; + + if (!strncasecmp(".chd", ext, 4)) + { + return load_chd(filename, table); + } + else if (!strncasecmp(".cue", ext, 4)) + { + return load_cue(filename, table); + } + + return 0; +} + +struct track_t +{ + uint32_t start_lba; + uint32_t end_lba; + uint32_t bcd; + uint32_t reserved; +}; + +struct disk_t +{ + uint32_t track_count; + uint32_t total_lba; + uint32_t total_bcd; + uint16_t libcrypt_mask; + uint16_t metadata; // lower 2 bits encode the region, 3rd bit is reset request, the other bits are reseved + track_t track[99]; +}; + +#define TIMEKEEPER_SIZE (8 * 1024) + +static void cdi_mount_save(const char *filename) +{ + user_io_set_index(1); + if (strlen(filename)) + { + FileGenerateSavePath(filename, buf, 0); + user_io_file_mount(buf, 1, 1, TIMEKEEPER_SIZE); + StoreIdx_S(1, buf); + } + else + { + user_io_file_mount("", 1); + StoreIdx_S(1, ""); + } +} + +static toc_t toc = {}; + +int cdi_chd_hunksize() +{ + if (toc.chd_f) + return toc.chd_hunksize; + + return 0; +} + +void cdi_read_cd(uint8_t *buffer, int lba, int cnt) +{ + // printf("req lba=%d, cnt=%d\n", lba, cnt); + + while (cnt > 0) + { + if (lba < toc.tracks[0].start || !toc.last) + { + memset(buffer, 0, CD_SECTOR_LEN); + } + else + { + memset(buffer, 0xAA, CD_SECTOR_LEN); + + for (int i = 0; i < toc.last; i++) + { + if (lba >= toc.tracks[i].start && lba <= toc.tracks[i].end) + { + if (!toc.chd_f) + { + if (toc.tracks[i].offset) + { + FileSeek(&toc.tracks[0].f, toc.tracks[i].offset + ((lba - toc.tracks[i].start) * CD_SECTOR_LEN), SEEK_SET); + } + else + { + FileSeek(&toc.tracks[i].f, (lba - toc.tracks[i].start) * CD_SECTOR_LEN, SEEK_SET); + } + } + while (cnt) + { + if (toc.tracks[i + 1].pregap && lba > (toc.tracks[i + 1].start - toc.tracks[i + 1].indexes[1])) + { + // The TOC is setup so that pregap sectors are actually part of the + // PREVIOUS track. If the pregap field is set the file doesn't contain + // this data, so we have to fake it. + // Check the next track's pregap and indexes[1] values to determine + // if we're reading pregap sectors + + memset(buffer, 0x0, CD_SECTOR_LEN); + } + else if (toc.chd_f) + { + + // The "fake" 150 sector pregap moves all the LBAs up by 150, so adjust here to read where the core actually wants data from + int read_lba = lba - toc.tracks[0].indexes[1]; + if (mister_chd_read_sector(toc.chd_f, (read_lba + toc.tracks[i].offset), 0, 0, CD_SECTOR_LEN, buffer, chd_hunkbuf, &chd_hunknum) == CHDERR_NONE) + { + if (!toc.tracks[i].type) // CHD requires byteswap of audio data + { + for (int swapidx = 0; swapidx < CD_SECTOR_LEN; swapidx += 2) + { + uint8_t temp = buffer[swapidx]; + buffer[swapidx] = buffer[swapidx + 1]; + buffer[swapidx + 1] = temp; + } + } + } + else + { + printf("\x1b[32mCDI: CHD read error: %d\n\x1b[0m", lba); + } + } + else + { + if (toc.tracks[i].offset) + FileReadAdv(&toc.tracks[0].f, buffer, CD_SECTOR_LEN); + else + FileReadAdv(&toc.tracks[i].f, buffer, CD_SECTOR_LEN); + } + if ((lba + 1) > toc.tracks[i].end) + break; + buffer += CD_SECTOR_LEN; + cnt--; + lba++; + } + break; + } + } + } + + buffer += CD_SECTOR_LEN; + cnt--; + lba++; + } +} + +static void mount_cd(int size, int index) +{ + spi_uio_cmd_cont(UIO_SET_SDINFO); + spi32_w(size); + spi32_w(0); + DisableIO(); + spi_uio_cmd8(UIO_SET_SDSTAT, (1 << index) | 0x80); + user_io_bufferinvalidate(1); +} + +void cdi_mount_cd(int s_index, const char *filename) +{ + int loaded = 0; + + if (strlen(filename)) + { + if (load_cd_image(filename, &toc) && toc.last) + { + cdi_mount_save(filename); + user_io_set_index(0); + mount_cd(toc.end * CD_SECTOR_LEN, s_index); + loaded = 1; + } + } + + if (!loaded) + { + printf("Unmount CD\n"); + unload_cue(&toc); + unload_chd(&toc); + mount_cd(0, s_index); + } +} + +void cdi_poll() +{ + spi_uio_cmd(UIO_CD_GET); +} diff --git a/support/cdi/cdi.h b/support/cdi/cdi.h new file mode 100644 index 0000000..80a85aa --- /dev/null +++ b/support/cdi/cdi.h @@ -0,0 +1,11 @@ +#ifndef CDI_H +#define CDI_H + +void cdi_mount_cd(int s_index, const char *filename); +void cdi_fill_blanksave(uint8_t *buffer, uint32_t lba, int cnt); +void cdi_read_cd(uint8_t *buffer, int lba, int cnt); +int cdi_chd_hunksize(); +const char* cdi_get_game_id(); +void cdi_poll(); + +#endif diff --git a/user_io.cpp b/user_io.cpp index 34e7c95..e7c5416 100644 --- a/user_io.cpp +++ b/user_io.cpp @@ -310,6 +310,13 @@ char is_psx() return (is_psx_type == 1); } +static int is_cdi_type = 0; +char is_cdi() +{ + if (!is_cdi_type) is_cdi_type = strcasecmp(orig_name, "CD-i") ? 2 : 1; + return (is_cdi_type == 1); +} + static int is_st_type = 0; char is_st() { @@ -2073,7 +2080,7 @@ int user_io_file_mount(const char *name, unsigned char index, char pre, int pre_ } buffer_lba[index] = -1; - if (!index) use_save = pre; + if (!index || is_cdi()) use_save = pre; if (!ret) { @@ -3017,7 +3024,10 @@ void user_io_poll() lba = spi_w(0); lba = (lba & 0xFFFF) | (((uint32_t)spi_w(0)) << 16); blks = ((c >> 9) & 0x3F) + 1; - blksz = (disk == 1 && is_psx()) ? 2352 : (128 << ((c >> 6) & 7)); + if ((disk == 0 && is_cdi()) || (disk == 1 && is_psx())) + blksz = 2352; + else + blksz = 128 << ((c >> 6) & 7); sz = blksz * blks; if (sz > sizeof(buffer[0])) @@ -3140,11 +3150,16 @@ void user_io_poll() else if (op & 1) { uint32_t buf_n = sizeof(buffer[0]) / blksz; - unsigned int psx_blksz = 0; if (is_psx() && blksz == 2352) { //returns 0 if the mounted disk is not a chd, otherwise returns the chd hunksize in bytes - psx_blksz = psx_chd_hunksize(); + unsigned int psx_blksz = psx_chd_hunksize(); + if (psx_blksz && psx_blksz <= sizeof(buffer[0])) buf_n = psx_blksz / blksz; + } + else if (is_cdi() && blksz == 2352) + { + //returns 0 if the mounted disk is not a chd, otherwise returns the chd hunksize in bytes + unsigned int psx_blksz = cdi_chd_hunksize(); if (psx_blksz && psx_blksz <= sizeof(buffer[0])) buf_n = psx_blksz / blksz; } //printf("SD RD (%llu,%d) on %d, WIDE=%d\n", lba, blksz, disk, fio_size); @@ -3162,6 +3177,13 @@ void user_io_poll() done = 1; buffer_lba[disk] = lba; } + else if (blksz == 2352 && is_cdi()) + { + diskled_on(); + cdi_read_cd(buffer[disk], lba, buf_n); + done = 1; + buffer_lba[disk] = lba; + } else if (sd_image[disk].size) { diskled_on(); @@ -3239,6 +3261,11 @@ void user_io_poll() psx_read_cd(buffer[disk], lba, buf_n); buffer_lba[disk] = lba; } + else if (blksz == 2352 && is_cdi()) + { + cdi_read_cd(buffer[disk], lba, buf_n); + buffer_lba[disk] = lba; + } else if (FileSeek(&sd_image[disk], lba * blksz, SEEK_SET) && FileReadAdv(&sd_image[disk], buffer[disk], sizeof(buffer[disk]))) { @@ -3538,6 +3565,7 @@ void user_io_poll() if (is_megacd()) mcd_poll(); if (is_pce()) pcecd_poll(); if (is_saturn()) saturn_poll(); + if (is_cdi()) cdi_poll(); if (is_psx()) psx_poll(); if (is_neogeo_cd()) neocd_poll(); if (is_n64()) n64_poll(); diff --git a/user_io.h b/user_io.h index 0abddce..7cab093 100644 --- a/user_io.h +++ b/user_io.h @@ -279,6 +279,7 @@ char is_gba(); char is_c64(); char is_st(); char is_psx(); +char is_cdi(); char is_arcade(); char is_saturn(); char is_pcxt();