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