From d8c338046ab3de0d1b5c17e196fb44c7c3f2e6ef Mon Sep 17 00:00:00 2001 From: sorgelig Date: Sat, 20 Jul 2019 10:37:08 +0800 Subject: [PATCH] Merge NeoGeo support code. --- Makefile | 1 + MiSTer.vcxproj | 4 + MiSTer.vcxproj.filters | 12 + menu.cpp | 38 ++- support.h | 3 + support/neogeo/graphics.cpp | 79 ++++++ support/neogeo/graphics.h | 4 + support/neogeo/loader.cpp | 361 ++++++++++++++++++++++++++ support/neogeo/loader.h | 9 + user_io.cpp | 486 +++++++++++++++++++++++++++++++++--- user_io.h | 20 +- 11 files changed, 975 insertions(+), 42 deletions(-) create mode 100644 support/neogeo/graphics.cpp create mode 100644 support/neogeo/graphics.h create mode 100644 support/neogeo/loader.cpp create mode 100644 support/neogeo/loader.h diff --git a/Makefile b/Makefile index 898efc9..34ea846 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ CPP_SRC = $(wildcard *.cpp) \ $(wildcard ./support/st/*.cpp) \ $(wildcard ./support/x86/*.cpp) \ $(wildcard ./support/snes/*.cpp) \ + $(wildcard ./support/neogeo/*.cpp) \ lib/lodepng/lodepng.cpp IMG = $(wildcard *.png) diff --git a/MiSTer.vcxproj b/MiSTer.vcxproj index f136b04..3c5e2a9 100644 --- a/MiSTer.vcxproj +++ b/MiSTer.vcxproj @@ -78,6 +78,8 @@ + + @@ -128,6 +130,8 @@ + + diff --git a/MiSTer.vcxproj.filters b/MiSTer.vcxproj.filters index 568acde..f7a2802 100644 --- a/MiSTer.vcxproj.filters +++ b/MiSTer.vcxproj.filters @@ -142,6 +142,12 @@ Source Files + + Source Files + + + Source Files + @@ -288,5 +294,11 @@ Header Files + + Header Files + + + Header Files + \ No newline at end of file diff --git a/menu.cpp b/menu.cpp index b72401b..ef31a7b 100644 --- a/menu.cpp +++ b/menu.cpp @@ -1574,10 +1574,25 @@ void HandleUI(void) case MENU_8BIT_MAIN_FILE_SELECTED: printf("File selected: %s\n", SelectedPath); - user_io_store_filename(SelectedPath); - user_io_file_tx(SelectedPath, user_io_ext_idx(SelectedPath, fs_pFileExt) << 6 | ioctl_index, opensave); - if(user_io_use_cheats()) cheats_init(SelectedPath, user_io_get_file_crc()); - menustate = MENU_NONE1; + if (is_neogeo_core()) + { + if (!neogeo_romset_tx(SelectedPath)) + { + OsdSetTitle("Message", 0); + OsdEnable(0); // do not disable keyboard + menu_timer = GetTimer(2000); + menustate = MENU_INFO; + } else { + menustate = MENU_NONE1; + } + } + else + { + user_io_store_filename(SelectedPath); + user_io_file_tx(SelectedPath, user_io_ext_idx(SelectedPath, fs_pFileExt) << 6 | ioctl_index, opensave); + if (user_io_use_cheats()) cheats_init(SelectedPath, user_io_get_file_crc()); + menustate = MENU_NONE1; + } break; case MENU_8BIT_MAIN_IMAGE_SELECTED: @@ -1591,6 +1606,21 @@ void HandleUI(void) user_io_set_index(user_io_ext_idx(SelectedPath, fs_pFileExt) << 6 | (menusub + 1)); user_io_file_mount(SelectedPath, drive_num); } + + if (is_neogeo_core()) + { + // ElectronAsh. + strcpy(SelectedPath + strlen(SelectedPath) - 3, "CUE"); + printf("Checking for presence of CUE file %s\n", SelectedPath); + if (user_io_file_mount(SelectedPath, 1)) + { + printf("CUE file found and mounted.\n"); + parse_cue_file(); + char str[2] = ""; + neogeo_romset_tx(str); + } + } + menustate = SelectedPath[0] ? MENU_NONE1 : MENU_8BIT_MAIN1; break; diff --git a/support.h b/support.h index 73ce1d9..ad5ca18 100644 --- a/support.h +++ b/support.h @@ -19,3 +19,6 @@ // SNES support #include "support/snes/snes.h" + +// NeoGeo support +#include "support/neogeo/loader.h" \ No newline at end of file diff --git a/support/neogeo/graphics.cpp b/support/neogeo/graphics.cpp new file mode 100644 index 0000000..f8fd3a2 --- /dev/null +++ b/support/neogeo/graphics.cpp @@ -0,0 +1,79 @@ +// Moves bytes around so that the fix graphics are stored in a way that +// takes advantage of the SDRAM 16-bit bus +// Part of Neogeo_MiSTer +// (C) 2019 Sean 'furrtek' Gonsalves + +#include "graphics.h" +#include "../../spi.h" + +void spr_convert(uint8_t* buf_in, uint8_t* buf_out, unsigned int size) +{ + /* + In C ROMs, a word provides two bitplanes for an 8-pixel wide line + They're used in pairs to provide 32 bits at once (all four bitplanes) + For one sprite tile, bytes are used like this: ([...] represents one 8-pixel wide line) + Even ROM Odd ROM + [ 40 41 ][ 00 01 ] [ 42 43 ][ 02 03 ] + [ 44 45 ][ 04 05 ] [ 46 47 ][ 06 07 ] + [ 48 49 ][ 08 09 ] [ 4A 4B ][ 0A 0B ] + [ 4C 4D ][ 0C 0D ] [ 4E 4F ][ 0E 0F ] + [ 50 51 ][ 10 11 ] [ 52 53 ][ 12 13 ] + ... ... + The data read for a given tile line (16 pixels) is always the same, only the rendering order of the pixels can change + To take advantage of the SDRAM burst read feature, the data can be loaded so that all 16 pixels of a tile + line can be read sequentially: () are 16-bit words, [] is the 4-word burst read + [(40 41) (00 01) (42 43) (02 03)] + [(44 45) (04 05) (46 47) (06 07)]... + Word interleaving is done on the FPGA side to mix the two C ROMs data (even/odd) + + In: FEDCBA9876 54321 0 + Out: FEDCBA9876 15432 0 + */ + for (unsigned int i = 0; i < size; i++) + buf_out[i] = buf_in[(i & 0xFFC0) | (i & 1) | ((i >> 1) & 0x1E) | (((i & 2) ^ 2) << 4)]; + + /* + 0 <- 20 + 1 <- 21 + 2 <- 00 + 3 <- 01 + 4 <- 22 + 5 <- 23 + 6 <- 02 + 7 <- 03 + ... + + 00 -> 02 + 01 -> 03 + 02 -> 06 + 03 -> 07 + ... + */ +} + +void fix_convert(uint8_t* buf_in, uint8_t* buf_out, unsigned int size) +{ + /* + In S ROMs, a byte provides two pixels + For one fix tile, bytes are used like this: ([...] represents a pair of pixels) + [10][18][00][08] + [11][19][01][09] + [12][1A][02][0A] + [13][1B][03][0B] + [14][1C][04][0C] + [15][1D][05][0D] + [16][1E][06][0E] + [17][1F][07][0F] + The data read for a given tile line (8 pixels) is always the same + To take advantage of the SDRAM burst read feature, the data can be loaded so that all 8 pixels of a tile + line can be read sequentially: () are 16-bit words, [] is the 2-word burst read + [(10 18) (00 08)] + [(11 19) (01 09)]... + + In: FEDCBA9876543210 + Out: FEDCBA9876510432 + */ + for (unsigned int i = 0; i < size; i++) + buf_out[i] = buf_in[(i & 0xFFE0) | ((i >> 2) & 7) | ((i & 1) << 3) | (((i & 2) << 3) ^ 0x10)]; +} + diff --git a/support/neogeo/graphics.h b/support/neogeo/graphics.h new file mode 100644 index 0000000..26a3a4d --- /dev/null +++ b/support/neogeo/graphics.h @@ -0,0 +1,4 @@ +#include "../../file_io.h" + +void spr_convert(uint8_t* buf_in, uint8_t* buf_out, unsigned int size); +void fix_convert(uint8_t* buf_in, uint8_t* buf_out, unsigned int size); diff --git a/support/neogeo/loader.cpp b/support/neogeo/loader.cpp new file mode 100644 index 0000000..d0c57f1 --- /dev/null +++ b/support/neogeo/loader.cpp @@ -0,0 +1,361 @@ +// Part of Neogeo_MiSTer +// (C) 2019 Sean 'furrtek' Gonsalves + +#include +#include +#include +#include // clock_gettime, CLOCK_REALTIME +#include "graphics.h" +#include "loader.h" +#include "../../sxmlc.h" +#include "../../user_io.h" +#include "../../osd.h" + +bool checked_ok; + +void neogeo_osd_progress(const char* name, unsigned int progress) { + char progress_buf[30 + 1]; + + // OSD width - width of white bar on the left - max width of file name = 32 - 2 - 11 - 1 = 18 + progress = (progress * 18) >> 8; + if (progress >= 18) progress = 18; + + // ############################## + // NNNNNNNNNNN-PPPPPPPPPPPPPPPPPP + memset(progress_buf, ' ', 30); + memcpy(progress_buf, name, strlen(name)); + for (unsigned int i = 0; i < progress; i++) + progress_buf[12 + i] = '#'; + progress_buf[30] = 0; + + OsdWrite(OsdGetSize() - 1, progress_buf, 0); +} + +int neogeo_file_tx(const char* romset, const char* name, unsigned char neo_file_type, unsigned char index, unsigned long offset, unsigned long size) +{ + fileTYPE f = {}; + uint8_t buf[4096]; // Same in user_io_file_tx + uint8_t buf_out[4096]; + static char name_buf[256]; + unsigned long bytes2send = size; + struct timespec ts1, ts2; // DEBUG PROFILING + long us_acc = 0; // DEBUG PROFILING + + if (!bytes2send) return 0; + + strcpy(name_buf, "/media/fat/NeoGeo/"); + if (strlen(romset)) { + strcat(name_buf, romset); + strcat(name_buf, "/"); + } + strcat(name_buf, name); + + if (!FileOpen(&f, name_buf, 0)) return 0; + + FileSeek(&f, offset, SEEK_SET); + + printf("Loading %s (offset %lu, size %lu, type %u) with index 0x%02X\n", name, offset, bytes2send, neo_file_type, index); + + // Put pairs of bitplanes in the correct order for the core + if (neo_file_type == NEO_FILE_SPR) index ^= 1; + // set index byte + user_io_set_index(index); + + // prepare transmission of new file + EnableFpga(); + spi8(UIO_FILE_TX); + spi8(0xff); + DisableFpga(); + + while (bytes2send) + { + uint16_t chunk = (bytes2send > sizeof(buf)) ? sizeof(buf) : bytes2send; + + FileReadAdv(&f, buf, chunk); + + EnableFpga(); + spi8(UIO_FILE_TX_DAT); + + if (neo_file_type == NEO_FILE_RAW) { + spi_write(buf, chunk, 1); + } else if (neo_file_type == NEO_FILE_8BIT) { + spi_write(buf, chunk, 0); + } else { + + if (neo_file_type == NEO_FILE_FIX) + fix_convert(buf, buf_out, sizeof(buf_out)); + else if (neo_file_type == NEO_FILE_SPR) + spr_convert(buf, buf_out, sizeof(buf_out)); + + clock_gettime(CLOCK_REALTIME, &ts1); // DEBUG PROFILING + spi_write(buf_out, chunk, 1); + clock_gettime(CLOCK_REALTIME, &ts2); // DEBUG PROFILING + + if (ts2.tv_nsec < ts1.tv_nsec) { // DEBUG PROFILING + ts2.tv_nsec += 1000000000; + ts2.tv_sec--; + } + + us_acc += ((ts2.tv_nsec - ts1.tv_nsec) / 1000); // DEBUG PROFILING + } + + DisableFpga(); + + neogeo_osd_progress(name, 256 - ((bytes2send << 8) / size)); + bytes2send -= chunk; + } + + // DEBUG PROFILING + printf("Gfx spi_write us total: %09ld\n", us_acc); + // mslug all C ROMs: + // spr_convert: 37680*4 = 150720us = 0.150s + // spi_write: 2300766*4 = 9203064us = 9.2s ! + + FileClose(&f); + + // signal end of transmission + EnableFpga(); + spi8(UIO_FILE_TX); + spi8(0x00); + DisableFpga(); + + return 1; +} + +static int xml_check_files(XMLEvent evt, const XMLNode* node, SXML_CHAR* text, const int n, SAX_Data* sd) +{ + const char* romset = (const char*)sd->user; + static int in_correct_romset = 0; + static char full_path[256]; + + switch (evt) + { + case XML_EVENT_START_NODE: + if (!strcasecmp(node->tag, "romset")) { + if (!strcasecmp(node->attributes[0].value, romset)) { + printf("Romset %s found !\n", romset); + in_correct_romset = 1; + } + else { + in_correct_romset = 0; + } + } + if (in_correct_romset) { + if (!strcasecmp(node->tag, "file")) { + for (int i = 0; i < node->n_attributes; i++) { + if (!strcasecmp(node->attributes[i].name, "name")) { + struct stat64 st; + sprintf(full_path, "%s/neogeo/%s/%s", getRootDir(), romset, node->attributes[i].value); + if (!stat64(full_path, &st)) { + printf("Found %s\n", full_path); + break; + } + else { + printf("Missing %s\n", full_path); + sprintf(full_path, "Missing %s !", node->attributes[i].value); + OsdWrite(OsdGetSize() - 1, full_path, 0); + return false; + } + } + } + } + } + break; + + case XML_EVENT_END_NODE: + if (in_correct_romset) { + if (!strcasecmp(node->tag, "romset")) { + checked_ok = true; + return false; + } + } + if (!strcasecmp(node->tag, "romsets")) { + printf("Couldn't find romset %s\n", romset); + return false; + } + break; + + case XML_EVENT_ERROR: + printf("XML parse: %s: ERROR %d\n", text, n); + break; + default: + break; + } + + return true; +} + +static int xml_load_files(XMLEvent evt, const XMLNode* node, SXML_CHAR* text, const int n, SAX_Data* sd) +{ + const char* romset = (const char*)sd->user; + static char file_name[16 + 1] { "" }; + static int in_correct_romset = 0; + static int in_file = 0; + static unsigned char file_index = 0; + static char file_type = 0; + static unsigned long int file_offset = 0, file_size = 0; + static unsigned char hw_type = 0, use_pcm = 0; + + switch (evt) + { + case XML_EVENT_START_NODE: + if (!strcasecmp(node->tag, "romset")) { + for (int i = 0; i < node->n_attributes; i++) { + if (!strcasecmp(node->attributes[i].name, "name")) { + if (!strcasecmp(node->attributes[i].value, romset)) { + printf("Romset %s found !\n", romset); + in_correct_romset = 1; + } else { + in_correct_romset = 0; + } + } else if (!strcasecmp(node->attributes[i].name, "hw")) { + hw_type = atoi(node->attributes[i].value); + } else if (!strcasecmp(node->attributes[i].name, "pcm")) { + use_pcm = atoi(node->attributes[i].value); + } + } + } + if (in_correct_romset) { + if (!strcasecmp(node->tag, "file")) { + for (int i = 0; i < node->n_attributes; i++) { + if (!strcasecmp(node->attributes[i].name, "name")) + strncpy(file_name, node->attributes[i].value, 16); + + if (!strcasecmp(node->attributes[i].name, "type")) { + file_type = *node->attributes[i].value; + if (file_type == 'S') + file_type = NEO_FILE_FIX; + else if (file_type == 'C') + file_type = NEO_FILE_SPR; + else if (file_type == 'M') + file_type = NEO_FILE_8BIT; + else + file_type = NEO_FILE_RAW; + } + + if (!strcasecmp(node->attributes[i].name, "index")) + file_index = atoi(node->attributes[i].value); + + if (!strcasecmp(node->attributes[i].name, "offset")) + file_offset = strtol(node->attributes[i].value, NULL, 0); + + if (!strcasecmp(node->attributes[i].name, "size")) + file_size = strtol(node->attributes[i].value, NULL, 0); + } + in_file = 1; + } + } + break; + + case XML_EVENT_END_NODE: + if (in_correct_romset) { + if (!strcasecmp(node->tag, "romset")) { + printf("Setting cart hardware type to %u\n", hw_type); + user_io_8bit_set_status(((uint32_t)hw_type & 3) << 24, 0x03000000); + printf("Setting cart to%s use the PCM chip\n", use_pcm ? "" : " not"); + user_io_8bit_set_status(((uint32_t)use_pcm & 1) << 26, 0x04000000); + return 0; + } else if (!strcasecmp(node->tag, "file")) { + if (in_file) + neogeo_file_tx(romset, file_name, file_type, file_index, file_offset, file_size); + in_file = 0; + } + } + break; + + case XML_EVENT_ERROR: + printf("XML parse: %s: ERROR %d\n", text, n); + break; + default: + break; + } + + return true; +} + +int neogeo_romset_tx(char* name) { + char romset[8 + 1]; + int system_type; + static char full_path[1024]; + + memset(romset, 0, sizeof(romset)); + + system_type = (user_io_8bit_set_status(0, 0) >> 1) & 3; + printf("System type: %u\n", system_type); + + user_io_8bit_set_status(1, 1); // Maintain reset + + // Look for the romset's file list in romsets.xml + if (!(system_type & 2)) { + // Get romset name from path (which should point to a .p1 or .ep1 file) + char *p = strrchr(name, '/'); + if (!p) return 0; + *p = 0; + p = strrchr(name, '/'); + if (!p) return 0; + strncpy(romset, p + 1, strlen(p + 1)); + + sprintf(full_path, "%s/neogeo/romsets.xml", getRootDir()); + SAX_Callbacks sax; + SAX_Callbacks_init(&sax); + + checked_ok = false; + sax.all_event = xml_check_files; + XMLDoc_parse_file_SAX(full_path, &sax, romset); + if (!checked_ok) return 0; + + sax.all_event = xml_load_files; + XMLDoc_parse_file_SAX(full_path, &sax, romset); + } + + // Load system ROMs + if (strcmp(romset, "debug")) { + // Not loading the special 'debug' romset + struct stat64 st; + if (!(system_type & 2)) { + sprintf(full_path, "%s/neogeo/uni-bios.rom", getRootDir()); + if (!stat64(full_path, &st)) { + // Autoload Unibios for cart systems if present + neogeo_file_tx("", "uni-bios.rom", NEO_FILE_RAW, 0, 0, 0x20000); + } else { + // Otherwise load normal system roms + if (system_type == 0) + neogeo_file_tx("", "neo-epo.sp1", NEO_FILE_RAW, 0, 0, 0x20000); + else + neogeo_file_tx("", "sp-s2.sp1", NEO_FILE_RAW, 0, 0, 0x20000); + } + } else if (system_type == 2) { + // NeoGeo CD + neogeo_file_tx("", "top-sp1.bin", NEO_FILE_RAW, 0, 0, 0x80000); + } else { + // NeoGeo CDZ + neogeo_file_tx("", "neocd.bin", NEO_FILE_RAW, 0, 0, 0x80000); + } + } + + if (!(system_type & 2)) + neogeo_file_tx("", "sfix.sfix", NEO_FILE_FIX, 2, 0, 0x10000); + neogeo_file_tx("", "000-lo.lo", NEO_FILE_8BIT, 1, 0, 0x10000); + + if (!strcmp(romset, "kof95")) { + printf("Enabled sprite gfx gap hack for kof95\n"); + user_io_8bit_set_status(0x10000000, 0x30000000); + } else if (!strcmp(romset, "whp")) { + printf("Enabled sprite gfx gap hack for whp\n"); + user_io_8bit_set_status(0x20000000, 0x30000000); + } else if (!strcmp(romset, "kizuna")) { + printf("Enabled sprite gfx gap hack for kizuna\n"); + user_io_8bit_set_status(0x30000000, 0x30000000); + } else + user_io_8bit_set_status(0x00000000, 0x30000000); + + if (!(system_type & 2)) + FileGenerateSavePath(name, (char*)full_path); + else + FileGenerateSavePath("ngcd", (char*)full_path); + user_io_file_mount((char*)full_path, 2, 1); + + user_io_8bit_set_status(0, 1); // Release reset + + return 1; +} diff --git a/support/neogeo/loader.h b/support/neogeo/loader.h new file mode 100644 index 0000000..292f1ca --- /dev/null +++ b/support/neogeo/loader.h @@ -0,0 +1,9 @@ +#include "../../file_io.h" + +#define NEO_FILE_RAW 0 +#define NEO_FILE_8BIT 1 +#define NEO_FILE_FIX 2 +#define NEO_FILE_SPR 3 + +extern bool checked_ok; +int neogeo_romset_tx(char* name); diff --git a/user_io.cpp b/user_io.cpp index 56cf364..d28b716 100644 --- a/user_io.cpp +++ b/user_io.cpp @@ -69,6 +69,63 @@ static bool caps_status = 0; static bool num_status = 0; static bool scrl_status = 0; +typedef struct +{ + bool track_active; + bool pregap_present; + uint8_t pre_m; // Actual "PREGAP". + uint8_t pre_s; + uint8_t pre_f; + bool ind0_present; + uint8_t ind0_m; // "Pregap" INDEX 00 + uint8_t ind0_s; + uint8_t ind0_f; + uint8_t ind1_m; // "Track Start" INDEX 01 + uint8_t ind1_s; + uint8_t ind1_f; + uint8_t type; // 0==AUDIO. 4==DATA. + int bytes_per_sec; +} cd_track_t; + +// Track 1-99, so entry zero is unused / ignored. +cd_track_t cd_trackinfo[100]; + +uint8_t cd_first_track; +uint8_t cd_last_track; + +static inline uint32_t msf_to_lba(uint8_t m, uint8_t s, uint8_t f) +{ + return (m*60*75) + (s*75) + f; +} + +uint32_t dec_2_bcd(uint32_t a) +{ + uint32_t result = 0; + int shift = 0; + + while (a != 0) + { + result |= (a % 10) << shift; + a /= 10; + shift += 4; + } + return result; +} + +uint32_t bcd_2_dec(uint32_t a) +{ + uint32_t result = 0; + uint32_t scale = 1; + + while (a != 0) + { + result += (a & 0x0f) * scale; + a >>= 4; + scale *= 10; + } + return result; +} + static char last_filename[1024] = {}; void user_io_store_filename(char *filename) { @@ -189,14 +246,25 @@ char is_snes_core() return (is_snes_type == 1); } +static int is_cpc_type = 0; char is_cpc_core() { - return !strcasecmp(core_name, "amstrad"); + if (!is_cpc_type) is_cpc_type = strcasecmp(core_name, "amstrad") ? 2 : 1; + return (is_cpc_type == 1); } +static int is_zx81_type = 0; char is_zx81_core() { - return !strcasecmp(core_name, "zx81"); + if (!is_zx81_type) is_zx81_type = strcasecmp(core_name, "zx81") ? 2 : 1; + return (is_zx81_type == 1); +} + +static int is_neogeo_type = 0; +char is_neogeo_core() +{ + if (!is_neogeo_type) is_neogeo_type = strcasecmp(core_name, "neogeo") ? 2 : 1; + return (is_neogeo_type == 1); } static int is_no_type = 0; @@ -215,6 +283,9 @@ static void user_io_read_core_name() is_x86_type = 0; is_no_type = 0; is_snes_type = 0; + is_cpc_type = 0; + is_zx81_type = 0; + is_neogeo_type = 0; core_name[0] = 0; // get core name @@ -822,10 +893,11 @@ void user_io_sd_set_config(void) } // read 8+32 bit sd card status word from FPGA -uint16_t user_io_sd_get_status(uint32_t *lba) +uint16_t user_io_sd_get_status(uint32_t *lba, uint16_t *req_type) { uint32_t s; uint16_t c; + uint16_t req = 0; spi_uio_cmd_cont(UIO_GET_SDSTAT); if (io_ver) @@ -833,6 +905,7 @@ uint16_t user_io_sd_get_status(uint32_t *lba) c = spi_w(0); s = spi_w(0); s = (s & 0xFFFF) | (((uint32_t)spi_w(0))<<16); + req = spi_w(0); } else { @@ -842,12 +915,16 @@ uint16_t user_io_sd_get_status(uint32_t *lba) s = (s << 8) | spi_in(); s = (s << 8) | spi_in(); s = (s << 8) | spi_in(); + req = spi_in(); } DisableIO(); if (lba) *lba = s; + if (req) + *req_type = req; + return c; } @@ -1609,6 +1686,225 @@ static int coldreset_req = 0; static uint32_t res_timer = 0; +uint8_t cd_lba_to_track(uint32_t req_lba) { + uint8_t track=1; + for (track=1; track<=cd_last_track; track++) { + uint32_t toc_lba = msf_to_lba(cd_trackinfo[track].ind1_m, cd_trackinfo[track].ind1_s, cd_trackinfo[track].ind1_f); // Convert track MSF to LBA. + //printf("TOC Track LBA: %08d\n", toc_lba); + if (req_lba > toc_lba) continue; // See if the TOC LBA is > the requested LBA. + else break; + } + return track-1; // The start LBA of the PREVIOUS track checked was lower than our requested LBA. +} + +int cue_pt = 0; +char cue_getch() +{ + static uint8_t buf[512]; + if (!(cue_pt & 0x1ff)) FileReadSec(&sd_image[1], buf); + if (cue_pt >= sd_image[1].size) return 0; + return buf[(cue_pt++) & 0x1ff]; +} + +char cue_readline(char *buffer) +{ + char my_char = 0; + bool ret = 0; + int char_count = 0; + + for (int i=0; i<1024; i++) { + ret = ( my_char = cue_getch() ); + if (my_char!=0x20) { // Ditch the spaces. + buffer[char_count] = my_char; + if (ret==0 || my_char==0x0A) { + buffer[char_count+1] = 0x00; // Null terminator. + break; + } + else char_count++; + } + } + return ret; +} + +void parse_cue_file(void) +{ + int i_num, i_min, i_sec, i_frame, bytes_per_sec = 0; + + // Clear the trackinfo before starting. + for (int i=0;i<=99;i++) + { + cd_trackinfo[i].track_active = 0; + cd_trackinfo[i].pregap_present = 0; + cd_trackinfo[i].pre_m = 0; + cd_trackinfo[i].pre_s = 0; + cd_trackinfo[i].pre_f = 0; + cd_trackinfo[i].ind0_present = 0; + cd_trackinfo[i].ind0_m = 0; + cd_trackinfo[i].ind0_s = 0; + cd_trackinfo[i].ind0_f = 0; + cd_trackinfo[i].ind1_m = 0; + cd_trackinfo[i].ind1_s = 0; + cd_trackinfo[i].ind1_f = 0; + cd_trackinfo[i].type = 0; + cd_trackinfo[i].bytes_per_sec = 0; + } + + size_t i_tracks = 0; + char str[1024]; + char type[5]; + + cue_pt = 0; // Set cue file index to zero. + + int track_num = 0; + bool first_track_done = 0; + + // Note: strncmp==0 means a MATCH! Because reasons. + + while( i_tracks < 99 ) + { + if ( !cue_readline(str) ) break; // Read in a whole line from the CUE file (until the end of the file). + + if ( strncmp(str, "TRACK", 5)==0 ) { // Is this a track? + sscanf( str, "%*5s%2u%5s%*1s%4u", &track_num, type, &bytes_per_sec); + if (!first_track_done) { + first_track_done = 1; + cd_first_track = track_num; + } + } + + if ( strncmp(str, "PREGAP", 6)==0 ) + { + sscanf( str, "%*6s%2u:%2u:%2u", &i_min, &i_sec, &i_frame ); + cd_trackinfo[track_num].pregap_present = 1; + cd_trackinfo[track_num].pre_m = i_min; + cd_trackinfo[track_num].pre_s = i_sec; + cd_trackinfo[track_num].pre_f = i_frame; + } + + if ( strncmp(str, "INDEX", 5)==0 ) // Is this an Index? + { + sscanf( str, "%*5s%2u%2u:%2u:%2u", &i_num, &i_min, &i_sec, &i_frame ); + + cd_trackinfo[track_num].track_active = 1; + if ( strcmp(type, "AUDIO")==0 ) { + cd_trackinfo[track_num].type = 0; + bytes_per_sec = 2352; // Audio tracks assume 2352 bytes per sector, so it's not listed in the CUE file. + } + else if ( strcmp(type, "MODE1")==0 ) { + cd_trackinfo[track_num].type = 4; + } + cd_trackinfo[track_num].bytes_per_sec = bytes_per_sec; + + /* + if (i_num==0) { // "Pregap" index, sort of. + printf("Track:%02d Pregap:%d M:%02d S:%02d F:%02d Type:%s TOCtype:%d BPS:%04d\n", track_num, cd_trackinfo[track_num].pregap_present, i_min, i_sec, i_frame, type, cd_trackinfo[track_num].type, bytes_per_sec); + cd_trackinfo[track_num].ind0_m = i_min; + cd_trackinfo[track_num].ind0_s = i_sec; + cd_trackinfo[track_num].ind0_f = i_frame; + } + */ + + if (i_num==1) { // "Track Start" index. + printf("Track:%02d Pregap:%d M:%02d S:%02d F:%02d Type:%s TOCtype:%d BPS:%04d\n", track_num, cd_trackinfo[track_num].pregap_present, i_min, i_sec, i_frame, type, cd_trackinfo[track_num].type, bytes_per_sec); + cd_trackinfo[track_num].ind1_m = i_min; + cd_trackinfo[track_num].ind1_s = i_sec; + cd_trackinfo[track_num].ind1_f = i_frame; + } + } + i_tracks++; + } + cd_last_track = track_num; +} + +void cd_generate_toc(uint16_t req_type, uint8_t *buffer) +{ + uint8_t m,s,f; + uint32_t lba; + + switch ( (req_type&0xFF00)>>8 ) { + case 0xD0: { // Request First Track and Last Track (BCD). + //buffer[0] = 0x01; // Rondo - First track (BCD). + //buffer[1] = 0x22; // Rondo - Last track (BCD). + buffer[0] = dec_2_bcd( cd_first_track ); + buffer[1] = dec_2_bcd( cd_last_track ); + buffer[2] = 0x00; // Padding. + buffer[3] = 0x00; // Padding. + printf("Core requesting CD TOC0. First Track:%02X. Last Track:%02X (BCD)\n", buffer[0], buffer[1]); + }; break; + + case 0xD1: { // Request Total Disk Size (MSF, in BCD). + //buffer[0] = 0x49; // Rondo - Minutes = 0x49 (73). + //buffer[1] = 0x09; // Rondo - Seconds = 0x09 (9). + //buffer[2] = 0x12; // Rondo - Frames = 0x12 (18). + + // ADD the PREGAP (if present). + /* + if (buffer[3]==4 && cd_trackinfo[track].pregap_present) { + m = cd_trackinfo[cd_last_track].ind1_m + cd_trackinfo[cd_last_track].pre_m; + s = cd_trackinfo[cd_last_track].ind1_s + cd_trackinfo[cd_last_track].pre_s; + f = cd_trackinfo[cd_last_track].ind1_f + cd_trackinfo[cd_last_track].pre_f; + // Not sure if audio tracks need the 2-second lead-in offset added? ElectronAsh. + uint32_t lba = msf_to_lba(m, s, f); // Convert to LBA, so we can add the 2-second lead-in. + //lba += 2*75; // Standard lead-in is 2 seconds (75 sectors per second, so 150). + // Convert back from LBA to MSF... + m = lba / (60 * 75); + lba -= m * (60 * 75); + s = lba / 75; + f = lba % 75; + + buffer[0] = dec_2_bcd( m ); + buffer[1] = dec_2_bcd( s ); + buffer[2] = dec_2_bcd( f ); + } + else + {*/ + buffer[0] = dec_2_bcd( cd_trackinfo[cd_last_track].ind1_m ); + buffer[1] = dec_2_bcd( cd_trackinfo[cd_last_track].ind1_s ); + buffer[2] = dec_2_bcd( cd_trackinfo[cd_last_track].ind1_f ); + //} + buffer[3] = 0x00; // Padding. + + printf("Core requesting CD TOC1. Total Disk Size:M:%02X S:%02X F:%02X (BCD)\n", buffer[0], buffer[1], buffer[2]); + }; break; + + case 0xD2: { // Request Track Info (Start MSF in BCD, and track type). + uint8_t track = bcd_2_dec(req_type&0xFF); // Track number from req_type upper byte is in BCD! + + // If a DATA track, check for a pregap, and ADD it (if present). + if (cd_trackinfo[track].type==4 && cd_trackinfo[track].pregap_present) { + m = cd_trackinfo[track].ind1_m + cd_trackinfo[track].pre_m; + s = cd_trackinfo[track].ind1_s + cd_trackinfo[track].pre_s; + f = cd_trackinfo[track].ind1_f + cd_trackinfo[track].pre_f; + + lba = msf_to_lba(m, s, f); // Convert to LBA, so we can add the 2-second lead-in. + lba += 2*75; // Standard lead-in is 2 seconds (75 sectors per second, so 150). + + // Convert back from LBA to MSF... + m = lba / (60 * 75); + lba -= m * (60 * 75); + s = lba / 75; + f = lba % 75; + + buffer[0] = dec_2_bcd( m ); + buffer[1] = dec_2_bcd( s ); + buffer[2] = dec_2_bcd( f ); + } + else + { + buffer[0] = dec_2_bcd( cd_trackinfo[track].ind1_m ); + buffer[1] = dec_2_bcd( cd_trackinfo[track].ind1_s ); + buffer[2] = dec_2_bcd( cd_trackinfo[track].ind1_f ); + } + buffer[3] = cd_trackinfo[track].type; + + printf("Core requesting CD TOC2. Track:%02d. M:%02X S:%02X F:%02X (BCD). Type:", track, buffer[0], buffer[1], buffer[2]); + if (buffer[3]==0x00) printf("AUDIO\n"); + else if (buffer[3]==0x04) printf("DATA\n"); + else printf("UNKNOWN!\n"); + }; break; + } +} + void user_io_poll() { if ((core_type != CORE_TYPE_MINIMIG2) && @@ -1772,7 +2068,8 @@ void user_io_poll() { static uint8_t buffer[4][512]; uint32_t lba; - uint16_t c = user_io_sd_get_status(&lba); + uint16_t req_type = 0; + uint16_t c = user_io_sd_get_status(&lba, &req_type); //if(c&3) printf("user_io_sd_get_status: cmd=%02x, lba=%08x\n", c, lba); // valid sd commands start with "5x" to avoid problems with @@ -1861,18 +2158,111 @@ void user_io_poll() //printf("SD RD %d on %d, WIDE=%d\n", lba, disk, fio_size); int done = 0; - - if (buffer_lba[disk] != lba) + if (is_neogeo_core()) { + uint32_t offset = 0; + if (sd_image[disk].size) { diskled_on(); - if (FileSeekLBA(&sd_image[disk], lba)) + printf("req_type: 0x%04X ", req_type); + switch ((req_type & 0xFF00) >> 8) { - if (FileReadSec(&sd_image[disk], buffer[disk])) + case 0xD0:case 0xD1:case 0xD2: + { + cd_generate_toc(req_type, buffer[disk]); + done = 1; + }; + break; + + case 0x48: + { + // Added this, Neo CD always requests by MSF (furrtek) + if ((req_type & 0xFF) == 0x01) { - done = 1; + printf("Neo CD requested raw lba value (MSF): 0x%08X\n", lba); + uint8_t m = bcd_2_dec((lba & 0xFF0000) >> 16); + uint8_t s = bcd_2_dec((lba & 0xFF00) >> 8); + uint8_t f = bcd_2_dec((lba & 0xFF) >> 0); + lba = msf_to_lba(m, s, f); + lba -= (2 * 75); // Remove 2 second pregap } + + uint8_t track = cd_lba_to_track(lba); + uint16_t bps = cd_trackinfo[track].bytes_per_sec; + uint32_t pregap = 0; + + if (cd_trackinfo[track].pregap_present) + { + pregap = msf_to_lba(cd_trackinfo[track].pre_m, cd_trackinfo[track].pre_s, cd_trackinfo[track].pre_f); + } + + if (bps == 2352) offset = 16 + ((lba - pregap) * 2352); // Rondo etc. + else if (bps == 2048) offset = ((lba - pregap) * 2048); // Homebrew, etc. + else printf("Data track %02d has unhandled bytes-per-sec of %d !\n", track, bps); + + if (FileSeek(&sd_image[disk], offset, SEEK_SET)) + { + if (FileReadAdv(&sd_image[disk], buffer[disk], 2048)) done = 1; + } + printf("Core requesting 2048-byte CD sector, from LBA: 0x%08X TRACK: %02d BPS: %04d OFFSET: 0x%08X \n", lba, track, bps, offset); + }; + break; + + case 0x52: + { + switch (req_type & 0xFF) + { + // "lba" holds the LBA. Dun do nothing. (no conversion needed). + case 0x00: + break; + + // "lba" holds the MSF (BCD). Convert to LBA. + case 0x01: + { + uint8_t m = bcd_2_dec((lba & 0xFF0000) >> 16); + uint8_t s = bcd_2_dec((lba & 0xFF00) >> 8); + uint8_t f = bcd_2_dec((lba & 0xFF) >> 0); + lba = msf_to_lba(m, s, f); + }; + break; + + // "lba" holds the TRACK number (BCD?). Grab the track start MSF from the TOC, then convert to LBA. + case 0x02: + { + uint8_t track = bcd_2_dec(lba); + lba = msf_to_lba(cd_trackinfo[track].ind1_m, cd_trackinfo[track].ind1_s, cd_trackinfo[track].ind1_f); + }; + break; + } + + uint8_t track = cd_lba_to_track(lba); + + if (cd_trackinfo[track].type != 0x00) + { + printf("Error: Core is trying to play back non-audio track as CDDA!\n"); + memset(buffer[disk], 0, sizeof(buffer[disk])); + } + else + { + if (FileSeek(&sd_image[disk], (lba - 525) * 2352, SEEK_SET)) + { + if (FileReadAdv(&sd_image[disk], buffer[disk], 2352)) done = 1; + } + } + printf("Core requesting a raw 2352-byte CD sector, from LBA: 0x%08X TRACK: %02d\n", lba, track); + }; + break; + + default: + { + if (FileSeekLBA(&sd_image[disk], lba)) + { + if (FileReadSec(&sd_image[disk], buffer[disk])) done = 1; + } + printf("Core requesting a 512-byte SD / VHD sector, from LBA: 0x%08X\n", lba); + }; + break; } } @@ -1880,37 +2270,66 @@ void user_io_poll() //Give an empty block. if (!done) memset(buffer[disk], 0, sizeof(buffer[disk])); buffer_lba[disk] = lba; - } - if(buffer_lba[disk] == lba) - { - //hexdump(buffer, 32, 0); - - // data is now stored in buffer. send it to fpga spi_uio_cmd_cont(UIO_SECTOR_RD); - spi_block_write(buffer[disk], fio_size); + if ((req_type & 0xF000) == 0xD000) spi_write(buffer[disk], 4, fio_size); // TOC. (4 bytes, including padding). + else if ((req_type & 0xFF00) == 0x4800) spi_write(buffer[disk], 2048, fio_size); // 2048-byte CD sector. + else if ((req_type & 0xFF00) == 0x5200) spi_write(buffer[disk], 2352, fio_size); // 2352-byte CD sector. + else spi_write(buffer[disk], 512, fio_size); // Standard 512-byte SD / VHD sector. DisableIO(); } - - // just load the next sector now, so it may be prefetched - // for the next request already - done = 0; - if (sd_image[disk].size) + else { - diskled_on(); - if (FileSeekLBA(&sd_image[disk], lba + 1)) + if (buffer_lba[disk] != lba) { - if (FileReadSec(&sd_image[disk], buffer[disk])) + if (sd_image[disk].size) { - done = 1; + diskled_on(); + if (FileSeekLBA(&sd_image[disk], lba)) + { + if (FileReadSec(&sd_image[disk], buffer[disk])) + { + done = 1; + } + } + } + + //Even after error we have to provide the block to the core + //Give an empty block. + if (!done) memset(buffer[disk], 0, sizeof(buffer[disk])); + buffer_lba[disk] = lba; + } + + if (buffer_lba[disk] == lba) + { + //hexdump(buffer, 32, 0); + + // data is now stored in buffer. send it to fpga + spi_uio_cmd_cont(UIO_SECTOR_RD); + spi_block_write(buffer[disk], fio_size); + DisableIO(); + } + + // just load the next sector now, so it may be prefetched + // for the next request already + done = 0; + if (sd_image[disk].size) + { + diskled_on(); + if (FileSeekLBA(&sd_image[disk], lba + 1)) + { + if (FileReadSec(&sd_image[disk], buffer[disk])) + { + done = 1; + } } } - } - if(done) buffer_lba[disk] = lba + 1; + if (done) buffer_lba[disk] = lba + 1; - if (sd_image[disk].type == 2) - { - buffer_lba[disk] = -1; + if (sd_image[disk].type == 2) + { + buffer_lba[disk] = -1; + } } } } @@ -1997,6 +2416,13 @@ void user_io_poll() } } + if (is_neogeo_core() && (!rtc_timer || CheckTimer(rtc_timer))) + { + // Update once per minute should be enough + rtc_timer = GetTimer(60000); + send_rtc(1); + } + if (core_type == CORE_TYPE_ARCHIE) archie_poll(); if (core_type == CORE_TYPE_SHARPMZ) sharpmz_poll(); diff --git a/user_io.h b/user_io.h index 7429a42..03aed34 100644 --- a/user_io.h +++ b/user_io.h @@ -182,9 +182,6 @@ typedef struct { void user_io_init(const char *path); unsigned char user_io_core_type(); -char is_minimig(); -char is_archie(); -char is_sharpmz(); void user_io_poll(); char user_io_menu_button(); char user_io_user_button(); @@ -198,9 +195,6 @@ int user_io_file_mount(char *name, unsigned char index = 0, char pre = 0); char user_io_serial_status(serial_status_t *, uint8_t); char *user_io_get_core_name(); const char *user_io_get_core_name_ex(); -char is_menu_core(); -char is_x86_core(); -char is_snes_core(); char has_menu(); const char *get_image_name(int i); @@ -236,8 +230,6 @@ void user_io_rtc_reset(); const char* get_rbf_dir(); const char* get_rbf_name(); -#define HomeDir (is_minimig() ? "Amiga" : is_archie() ? "Archie" : is_menu_core() ? "Scripts" : user_io_get_core_name()) - int GetUARTMode(); int GetMidiLinkMode(); void SetMidiLinkMode(int mode); @@ -252,4 +244,16 @@ void diskled_on(); #define DISKLED_ON diskled_on() #define DISKLED_OFF void() +void parse_cue_file(void); + +char is_minimig(); +char is_archie(); +char is_sharpmz(); +char is_menu_core(); +char is_x86_core(); +char is_snes_core(); +char is_neogeo_core(); + +#define HomeDir (is_minimig() ? "Amiga" : is_archie() ? "Archie" : is_menu_core() ? "Scripts" : user_io_get_core_name()) + #endif // USER_IO_H