Forráskód Böngészése

Implementing the Classic Mac Toolbox vendor code

Code was taken from BlueSCSI's https://github.com/BlueSCSI/BlueSCSI-v2/pull/79
and modified for use in ZuluSCSI.

Changes include the vendor page so the BluSCSI toolbox transfer
application will not work with this build.

Also changed was excluded files from the toolbox's CD changer.
Now subdirectories and common extensions compressed files and documents
are excluded from the list.

All files for the toolbox transfer app are now included.

Co-authored-by: Eric Helgeson <erichelgeson@gmail.com>
Morio 1 éve
szülő
commit
c5df0f9085

+ 1 - 0
lib/SCSI2SD/src/firmware/disk.h

@@ -54,5 +54,6 @@ void scsiDiskInit(void);
 void scsiDiskReset(void);
 void scsiDiskPoll(void);
 int scsiDiskCommand(void);
+int doTestUnitReady();
 
 #endif

+ 17 - 0
lib/SCSI2SD/src/firmware/mode.c

@@ -22,6 +22,7 @@
 #include "disk.h"
 #include "inquiry.h"
 #include "ZuluSCSI_mode.h"
+#include "toolbox.h"
 
 #include <string.h>
 
@@ -246,6 +247,15 @@ static const uint8_t AppleVendorPage[] =
 'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C',' ',' ',' '
 };
 
+static const uint8_t ToolboxVendorPage[] =
+{
+0x31, // Page code
+42,   // Page length
+'Z','u','l','u','S','C','S','I',' ','i','s',' ','G','P','L','v','3',' ','F','T','W',
+' ','R','a','b','b','i','t','H','o','l','e','C','o','m','p','u','t','i','n','g',0x00
+};
+
+
 static void pageIn(int pc, int dataIdx, const uint8_t* pageData, int pageLen)
 {
 	memcpy(&scsiDev.data[dataIdx], pageData, pageLen);
@@ -525,6 +535,13 @@ static void doModeSense(
 		idx += sizeof(AppleVendorPage);
 	}
 
+	if (scsiToolboxEnabled() && (pageCode == 0x31 || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, ToolboxVendorPage, sizeof(ToolboxVendorPage));
+		idx += sizeof(ToolboxVendorPage);
+	}
+
 	if (pageCode == 0x38) // Don't send unless requested
 	{
 		pageFound = 1;

+ 23 - 3
lib/SCSI2SD/src/firmware/scsi.c

@@ -33,8 +33,8 @@
 #include "tape.h"
 #include "mo.h"
 #include "vendor.h"
-
 #include <string.h>
+#include "toolbox.h"
 
 // Global SCSI device state.
 ScsiDevice scsiDev S2S_DMA_ALIGN;
@@ -287,7 +287,7 @@ static void process_DataOut()
 	}
 }
 
-static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 6, 12, 6, 6};
+static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 16, 12, 6, 6};
 static void process_Command()
 {
 	int group;
@@ -302,6 +302,8 @@ static void process_Command()
 
 	group = scsiDev.cdb[0] >> 5;
 	scsiDev.cdbLen = CmdGroupBytes[group];
+	scsiVendorCommandSetLen(scsiDev.cdb[0], &scsiDev.cdbLen);
+	
 	if (parityError &&
 		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
 	{
@@ -610,7 +612,25 @@ static void process_Command()
 	{
 		scsiReadBuffer();
 	}
-	else if (!scsiModeCommand() && !scsiVendorCommand())
+	else if (scsiModeCommand())
+	{
+		// handled
+	}
+	else if (scsiVendorCommand())
+	{
+		// handled
+	}
+	else if (unlikely(command == 0x00))
+    {
+        // TEST UNIT READY
+        doTestUnitReady();
+    }
+    else if (unlikely(!doTestUnitReady()))
+    {
+		// This should be last as it can override other commands 
+        // Status and sense codes already set by doTestUnitReady
+    }
+	else
 	{
 		scsiDev.target->sense.code = ILLEGAL_REQUEST;
 		scsiDev.target->sense.asc = INVALID_COMMAND_OPERATION_CODE;

+ 1 - 1
lib/SCSI2SD/src/firmware/scsi.h

@@ -145,7 +145,7 @@ typedef struct
 	int savedDataPtr; // Index into data, initially 0.
 	int dataLen;
 
-	uint8_t cdb[12]; // command descriptor block
+	uint8_t cdb[16]; // command descriptor block
 	uint8_t cdbLen; // 6, 10, or 12 byte message.
 	int8_t lun; // Target lun, set by IDENTIFY message.
 	uint8_t discPriv; // Disconnect priviledge.

+ 28 - 0
lib/SCSI2SD/src/firmware/toolbox.h

@@ -0,0 +1,28 @@
+/** 
+ * Copyright (C) 2023 Eric Helgeson
+ * Copyright (C) 2024 Rabbit Hole Computing
+ * 
+ * This file is originally part of BlueSCSI adopted for ZuluSCSI
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#ifndef S2S_TOOLBOX_H
+#define S2S_TOOLBOX_H
+#include <inttypes.h>
+int8_t scsiToolboxEnabled(void);
+void scsiToolboxCBDLen(uint8_t command, uint8_t *command_length);
+int scsiToolboxCommand(void);
+
+#endif

+ 12 - 0
lib/SCSI2SD/src/firmware/vendor.c

@@ -18,6 +18,7 @@
 #include "scsi.h"
 #include "vendor.h"
 #include "diagnostic.h"
+#include "toolbox.h"
 
 // Callback after the DATA OUT phase is complete.
 static void doAssignDiskParameters(void)
@@ -80,6 +81,10 @@ int scsiVendorCommand()
 		scsiDev.phase = DATA_OUT;
 		scsiDev.postDataOutHook = doWriteBuffer;
 	}
+	else if (scsiToolboxEnabled() && scsiToolboxCommand())
+	{
+		// already handled
+	}
 	else
 	{
 		commandHandled = 0;
@@ -88,3 +93,10 @@ int scsiVendorCommand()
 	return commandHandled;
 }
 
+void scsiVendorCommandSetLen(uint8_t command, uint8_t* command_length)
+{
+	if (scsiToolboxEnabled())
+	{
+		scsiToolboxCBDLen(command, command_length);
+	}
+}

+ 2 - 1
lib/SCSI2SD/src/firmware/vendor.h

@@ -18,5 +18,6 @@
 #define S2S_VENDOR_H
 
 int scsiVendorCommand(void);
-
+// Set the command length for applicable vendor commands
+void scsiVendorCommandSetLen(uint8_t command, uint8_t* command_length);
 #endif

+ 1 - 6
lib/ZuluSCSI_platform_RP2040/scsi_accel_target.cpp

@@ -907,12 +907,7 @@ void scsi_accel_rp2040_init()
         dma_channel_claim(SCSI_DMA_CH_D);
         g_channels_claimed = true;
     }
-
-#ifndef ZULUSCSI_NETWORK
-    // Load PIO programs
-    pio_clear_instruction_memory(SCSI_DMA_PIO);
-#endif // ZULUSCSI_NETWORK
-
+    
     // Parity lookup generator
     g_scsi_dma.pio_offset_parity = pio_add_program(SCSI_DMA_PIO, &scsi_parity_program);
     g_scsi_dma.pio_cfg_parity = scsi_parity_program_get_default_config(g_scsi_dma.pio_offset_parity);

+ 2 - 1
platformio.ini

@@ -98,6 +98,7 @@ lib_deps =
     ZuluSCSI_platform_RP2040
     SCSI2SD
     CUEParser
+upload_protocol = cmsis-dap
 debug_build_flags =
     -O2 -ggdb -g3
 ; The values can be adjusted down to get a debug build to fit in to SRAM
@@ -176,7 +177,7 @@ build_flags =
     -DZULUSCSI_DAYNAPORT
 ; These take a large portion of the SRAM and can be adjusted
     -DLOGBUFSIZE=8192
-    -DPREFETCH_BUFFER_SIZE=6144
+    -DPREFETCH_BUFFER_SIZE=4096
     -DSCSI2SD_BUFFER_SIZE=57344
     ; This controls the depth of 2 x NETWORK_PACKET_MAX_SIZE (1520 bytes)
     ; For example a queue size of 10 would be 10 x 2 x 1520 = 30400 bytes

+ 419 - 0
src/Toolbox.cpp

@@ -0,0 +1,419 @@
+/** 
+ * Copyright (C) 2023 Eric Helgeson
+ * Copyright (C) 2024 Rabbit Hole Computing
+ * This file is originally part of BlueSCSI adopted for ZuluSCSI
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+#include "Toolbox.h"
+#include "ZuluSCSI_disk.h"
+#include "ZuluSCSI_cdrom.h"
+#include "ZuluSCSI_log.h"
+#include <minIni.h>
+#include <SdFat.h>
+extern "C" {
+#include <toolbox.h>
+#include <scsi2sd_time.h>
+#include <sd.h>
+#include <mode.h>
+}
+
+
+extern "C" int8_t scsiToolboxEnabled()
+{
+    static int8_t enabled = -1;
+    if (enabled == -1)
+    {
+        enabled = ini_getbool("SCSI", "EnableToolbox", 0, CONFIGFILE);
+    }
+    return enabled == 1;
+}
+
+extern "C" void scsiToolboxCBDLen(uint8_t command, uint8_t* command_length)
+{
+  if (0xD0 <= command && command <= 0xDA)
+  {
+    *command_length = 10;
+  }
+}
+
+static bool toolboxFilenameValid(const char* name, bool isCD = false)
+{
+    if(strlen(name) == 0)
+    {
+        dbgmsg("toolbox: Ignoring filename empty file name");
+        return false;
+    }
+    if (isCD)
+    {
+      return scsiDiskFilenameValid(name);
+    }
+    return true;
+}
+
+static void doCountFiles(const char * dir_name, bool isCD = false)
+{
+    FsFile dir;
+    FsFile file;
+    char name[MAX_FILE_PATH] = {0};
+    dir.open(dir_name);
+    dir.rewindDirectory();
+    uint8_t file_count = 0;
+    while (file.openNext(&dir, O_RDONLY))
+    {
+        if(file.getError() > 0)
+        {
+            file.close();
+            break;
+        }
+        bool isDir = file.isDirectory();
+        file.getName(name, MAX_FILE_PATH);
+        file.close();
+        if (isCD && isDir)
+            continue;
+        // only count valid files.
+        if(toolboxFilenameValid(name, isCD))
+        {
+            file_count = file_count + 1;
+            if(file_count > 100) {
+                scsiDev.status = CHECK_CONDITION;
+                scsiDev.target->sense.code = ILLEGAL_REQUEST;
+                scsiDev.target->sense.asc = OPEN_RETRO_SCSI_TOO_MANY_FILES;
+                scsiDev.phase = STATUS;
+                dir.close();
+                return;
+            }
+        }
+  }
+  scsiDev.data[0] = file_count;
+  scsiDev.dataLen = sizeof(file_count);
+  scsiDev.phase = DATA_IN;
+}
+
+static void onListFiles(const char * dir_name, bool isCD = false) {
+    FsFile dir;
+    FsFile file;
+
+    memset(scsiDev.data, 0, 4096);
+    int ENTRY_SIZE = 40;
+    char name[MAX_FILE_PATH] = {0};
+    dir.open(dir_name);
+    dir.rewindDirectory();
+    uint8_t index = 0;
+    uint8_t file_entry[40] = {0};
+    while (file.openNext(&dir, O_RDONLY))
+    {   
+        uint8_t isDir = file.isDirectory() ? 0x00 : 0x01;
+        int len = file.getName(name, MAX_FILE_PATH);
+        if (len > MAX_MAC_PATH)
+            name[MAX_MAC_PATH] = 0x0;
+        uint64_t size = file.fileSize();
+        file.close();
+        if (!toolboxFilenameValid(name, isCD))
+            continue;
+        if (isCD && isDir == 0x00)
+            continue;
+        file_entry[0] = index;
+        file_entry[1] = isDir;
+        int c = 0; // Index of char in name[]
+
+        for(int i = 2; i < (MAX_MAC_PATH + 1 + 2); i++) {   // bytes 2 - 34
+            file_entry[i] = name[c++];
+        }
+        file_entry[35] = 0; //(size >> 32) & 0xff;
+        file_entry[36] = (size >> 24) & 0xff;
+        file_entry[37] = (size >> 16) & 0xff;
+        file_entry[38] = (size >> 8) & 0xff;
+        file_entry[39] = (size) & 0xff;
+        memcpy(&(scsiDev.data[ENTRY_SIZE * index]), file_entry, ENTRY_SIZE);
+        index = index + 1;
+    }
+    dir.close();
+
+    scsiDev.dataLen = 4096;
+    scsiDev.phase = DATA_IN;
+}
+
+static FsFile get_file_from_index(uint8_t index, const char * dir_name, bool isCD = false)
+{
+  FsFile dir;
+  FsFile file_test;
+  char name[MAX_FILE_PATH] = {0};
+
+  dir.open(dir_name);
+  dir.rewindDirectory(); // Back to the top
+  int count = 0;
+  while (file_test.openNext(&dir, O_RDONLY))
+  {
+    
+    // If error there is no next file to open.
+    if(file_test.getError() > 0) {
+      file_test.close();
+      break;
+    }
+    file_test.getName(name, MAX_FILE_PATH);
+    if (isCD && file_test.isDirectory())
+    {
+        file_test.close();
+        continue;
+    }
+    if(!toolboxFilenameValid(name, isCD))
+    {
+        file_test.close();
+        continue;
+    }
+    if (count == index)
+    {
+      dir.close();
+      return file_test;
+    }
+    else
+    {
+      file_test.close();
+    }
+    count++;
+  }
+  file_test.close();
+  dir.close();
+  return file_test;
+}
+
+// Devices that are active on this SCSI device.
+static void onListDevices()
+{
+  for (int i = 0; i < NUM_SCSIID; i++)
+  {
+    const S2S_TargetCfg* cfg = s2s_getConfigById(i);
+    if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
+    {
+        scsiDev.data[i] = (int)cfg->deviceType; // 2 == cd
+    }
+    else
+    {
+        scsiDev.data[i] = 0xFF; // not enabled target.
+    }
+  }
+  scsiDev.dataLen = NUM_SCSIID;
+}
+
+static void onSetNextCD(const char * img_dir)
+{
+    char name[MAX_FILE_PATH] = {0};
+    char full_path[MAX_FILE_PATH * 2] = {0};
+    uint8_t file_index = scsiDev.cdb[1];
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    FsFile next_cd = get_file_from_index(file_index, img_dir, true);
+    next_cd.getName(name, sizeof(name));
+    next_cd.close();
+    snprintf(full_path, (MAX_FILE_PATH * 2), "%s/%s", img_dir, name);
+    cdromSwitchNextImage(img, full_path);
+}
+
+FsFile gFile; // global so we can keep it open while transfering.
+void onGetFile10(char * dir_name) {
+    uint8_t index = scsiDev.cdb[1];
+
+    uint32_t offset = ((uint32_t)scsiDev.cdb[2] << 24) | ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
+
+    if (offset == 0) // first time, open the file.
+    {
+        gFile = get_file_from_index(index, dir_name);
+        if(!gFile.isDirectory() && !gFile.isReadable())
+        {
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            //SCSI_ASC_INVALID_FIELD_IN_CDB
+            scsiDev.phase = STATUS;
+            return;
+        }
+    }
+
+    uint32_t file_total = gFile.size();
+    memset(scsiDev.data, 0, 4096);
+    gFile.seekSet(offset * 4096);
+    int bytes_read = gFile.read(scsiDev.data, 4096);
+    if(offset * 4096 >= file_total) // transfer done, close.
+    {
+        gFile.close();
+    }
+    scsiDev.dataLen = bytes_read;
+    scsiDev.phase = DATA_IN;
+}
+
+/*
+  Prepares a file for receving. The file name is null terminated in the scsi data.
+*/
+static void onSendFilePrep(char * dir_name)
+{
+    char file_name[32+1];
+    memset(file_name, '\0', 32+1);
+    scsiEnterPhase(DATA_OUT);
+    for (int i = 0; i < 32+1; ++i)
+    {
+        file_name[i] = scsiReadByte();
+    }
+    SD.chdir(dir_name);
+    gFile.open(file_name, FILE_WRITE);
+    SD.chdir("/");
+    if(gFile.isOpen() && gFile.isWritable())
+    {
+        gFile.rewind();
+        gFile.sync();
+        // do i need to manually set phase to status here?
+        return;
+    } else {
+        gFile.close();
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        //SCSI_ASC_INVALID_FIELD_IN_CDB
+        scsiDev.phase = STATUS;
+    }
+}
+
+static void onSendFileEnd(void)
+{
+    gFile.sync();
+    gFile.close();
+    scsiDev.phase = STATUS;
+}
+
+static void onSendFile10(void)
+{
+    if(!gFile.isOpen() || !gFile.isWritable())
+    {
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        //SCSI_ASC_INVALID_FIELD_IN_CDB
+        scsiDev.phase = STATUS;
+    }
+
+    // Number of bytes sent this request, 1..512.
+    uint16_t bytes_sent = ((uint16_t)scsiDev.cdb[1] << 8)  | scsiDev.cdb[2];
+    // 512 byte offset of where to put these bytes.
+    uint32_t offset     = ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
+    uint16_t buf_size   = 512;
+    uint8_t buf[512];
+
+    // Check if last block of file, and not the only bock in file.
+    if(bytes_sent < buf_size)
+    {
+        buf_size = bytes_sent;
+    }
+
+    scsiEnterPhase(DATA_OUT);
+    scsiRead(buf, bytes_sent, NULL);
+    gFile.seekCur(offset * 512);
+    gFile.write(buf, buf_size);
+    if(gFile.getWriteError())
+    {
+        gFile.clearWriteError();
+        gFile.close();
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+    }
+    //scsiDev.phase = STATUS;
+}
+static void onToggleDebug()
+{
+    if(scsiDev.cdb[1] == 0) // 0 == Set Debug, 1 == Get Debug State
+    {
+        g_log_debug = scsiDev.cdb[2];
+        logmsg("Set debug logs to: ", g_log_debug);
+        scsiDev.phase = STATUS;
+    }
+    else
+    {
+        logmsg("Debug currently set to: ", g_log_debug);
+        scsiDev.data[0] = g_log_debug ? 0x1 : 0x0;
+        scsiDev.dataLen = 1;
+        scsiDev.phase = DATA_IN;
+    }
+}
+
+static int getToolBoxSharedDir(char * dir_name)
+{
+  return ini_gets("SCSI", "ToolBoxSharedDir", "/shared", dir_name, MAX_FILE_PATH, CONFIGFILE);
+}
+
+extern "C" int scsiToolboxCommand()
+{
+    int commandHandled = 1;
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    uint8_t command = scsiDev.cdb[0];
+
+    if (unlikely(command == TOOLBOX_COUNT_FILES))
+    {
+        char img_dir[MAX_FILE_PATH];
+        getToolBoxSharedDir(img_dir);
+        doCountFiles(img_dir);
+    }
+    else if (unlikely(command == TOOLBOX_LIST_FILES))
+    {
+        char img_dir[MAX_FILE_PATH];
+        getToolBoxSharedDir(img_dir);
+        onListFiles(img_dir);
+    }
+    else if (unlikely(command == TOOLBOX_GET_FILE))
+    {
+        char img_dir[MAX_FILE_PATH];
+        getToolBoxSharedDir(img_dir);
+        onGetFile10(img_dir);
+    }
+    else if (unlikely(command == TOOLBOX_SEND_FILE_PREP))
+    {
+        char img_dir[MAX_FILE_PATH];
+        getToolBoxSharedDir(img_dir);
+        onSendFilePrep(img_dir);
+    }
+    else if (unlikely(command == TOOLBOX_SEND_FILE_10))
+    {
+        onSendFile10();
+    }
+    else if (unlikely(command == TOOLBOX_SEND_FILE_END))
+    {
+        onSendFileEnd();
+    }
+    else if(unlikely(command == TOOLBOX_TOGGLE_DEBUG))
+    {
+        onToggleDebug();
+    }
+    else if(unlikely(command == TOOLBOX_LIST_CDS))
+    {
+        char img_dir[4];
+        snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, (int)img.scsiId & S2S_CFG_TARGET_ID_BITS);
+        onListFiles(img_dir, true);
+    }
+    else if(unlikely(command == TOOLBOX_SET_NEXT_CD))
+    {
+        char img_dir[4];
+        snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, (int)img.scsiId & S2S_CFG_TARGET_ID_BITS);
+        onSetNextCD(img_dir);
+    }
+    else if(unlikely(command == TOOLBOX_LIST_DEVICES))
+    {
+        onListDevices();
+    }
+    else if (unlikely(command == TOOLBOX_COUNT_CDS))
+    {
+        char img_dir[4];
+        snprintf(img_dir, sizeof(img_dir), CD_IMG_DIR, (int)img.scsiId & S2S_CFG_TARGET_ID_BITS);
+        doCountFiles(img_dir, true);
+    }
+    else
+    {
+        commandHandled = 0;
+    }
+
+    return commandHandled;
+}

+ 37 - 0
src/Toolbox.h

@@ -0,0 +1,37 @@
+/** 
+ * Copyright (C) 2023 Eric Helgeson
+ * 
+ * This file is part of BlueSCSI
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#pragma once
+
+#define MAX_MAC_PATH 32
+#define CD_IMG_DIR "CD%d"
+
+
+#define TOOLBOX_LIST_FILES     0xD0
+#define TOOLBOX_GET_FILE       0xD1
+#define TOOLBOX_COUNT_FILES    0xD2
+#define TOOLBOX_SEND_FILE_PREP 0xD3
+#define TOOLBOX_SEND_FILE_10   0xD4
+#define TOOLBOX_SEND_FILE_END  0xD5
+#define TOOLBOX_TOGGLE_DEBUG   0xD6
+#define TOOLBOX_LIST_CDS       0xD7
+#define TOOLBOX_SET_NEXT_CD    0xD8
+#define TOOLBOX_LIST_DEVICES   0xD9
+#define TOOLBOX_COUNT_CDS      0xDA
+#define OPEN_RETRO_SCSI_TOO_MANY_FILES 0x0001

+ 16 - 3
src/ZuluSCSI_cdrom.cpp

@@ -1208,12 +1208,20 @@ void cdromReinsertFirstImage(image_config_t &img)
 }
 
 // Check if we have multiple CD-ROM images to cycle when drive is ejected.
-bool cdromSwitchNextImage(image_config_t &img)
+bool cdromSwitchNextImage(image_config_t &img, const char* next_filename)
 {
     // Check if we have a next image to load, so that drive is closed next time the host asks.
-    char filename[MAX_FILE_PATH];
+    
     int target_idx = img.scsiId & 7;
-    scsiDiskGetNextImageName(img, filename, sizeof(filename));
+    char filename[MAX_FILE_PATH];
+    if (next_filename == nullptr)
+    {
+        scsiDiskGetNextImageName(img, filename, sizeof(filename));
+    }
+    else
+    {
+        strncpy(filename, next_filename, MAX_FILE_PATH);
+    }
 
 #ifdef ENABLE_AUDIO_OUTPUT
     // if in progress for this device, terminate audio playback immediately (Annex C)
@@ -1230,6 +1238,11 @@ bool cdromSwitchNextImage(image_config_t &img)
 
         if (status)
         {
+            if (next_filename != nullptr)
+            {
+                img.ejected = false;
+                img.cdrom_events = 2; // New Media
+            }
             return true;
         }
     }

+ 1 - 1
src/ZuluSCSI_cdrom.h

@@ -22,7 +22,7 @@ void cdromPerformEject(image_config_t &img);
 void cdromReinsertFirstImage(image_config_t &img);
 
 // Switch to next CD-ROM image if multiple have been configured
-bool cdromSwitchNextImage(image_config_t &img);
+bool cdromSwitchNextImage(image_config_t &img,  const char* next_filename = nullptr);
 
 // Check if the currently loaded cue sheet for the image can be parsed
 // and print warnings about unsupported track types

+ 2 - 11
src/ZuluSCSI_disk.cpp

@@ -1137,8 +1137,8 @@ static void doReadCapacity()
 /*************************/
 /* TestUnitReady command */
 /*************************/
-
-static int doTestUnitReady()
+extern "C"
+int doTestUnitReady()
 {
     int ready = 1;
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
@@ -1758,15 +1758,6 @@ int scsiDiskCommand()
             scsiDev.target->started = 0;
         }
     }
-    else if (unlikely(command == 0x00))
-    {
-        // TEST UNIT READY
-        doTestUnitReady();
-    }
-    else if (unlikely(!doTestUnitReady()))
-    {
-        // Status and sense codes already set by doTestUnitReady
-    }
     else if (likely(command == 0x08))
     {
         // READ(6)

+ 1 - 1
src/ZuluSCSI_disk.h

@@ -142,4 +142,4 @@ void scsiDiskStartRead(uint32_t lba, uint32_t blocks);
 void scsiDiskStartWrite(uint32_t lba, uint32_t blocks);
 
 // Returns true if there is at least one network device active
-bool scsiDiskCheckAnyNetworkDevicesConfigured();
+bool scsiDiskCheckAnyNetworkDevicesConfigured();