瀏覽代碼

Merge pull request #229 from saybur/image-directories

CD-ROM image listing from directories
Alex Perez 2 年之前
父節點
當前提交
3a2de5fb56
共有 5 個文件被更改,包括 227 次插入60 次删除
  1. 4 39
      src/ZuluSCSI.cpp
  2. 3 7
      src/ZuluSCSI_cdrom.cpp
  3. 3 0
      src/ZuluSCSI_config.h
  4. 201 10
      src/ZuluSCSI_disk.cpp
  5. 16 4
      src/ZuluSCSI_disk.h

+ 4 - 39
src/ZuluSCSI.cpp

@@ -373,51 +373,16 @@ bool findHDDImages()
       bool is_tp = (tolower(name[0]) == 't' && tolower(name[1]) == 'p');
       if (is_hd || is_cd || is_fd || is_mo || is_re || is_tp)
       {
-        // Check file extension
-        // We accept anything except known compressed files
-        bool is_compressed = false;
-        const char *extension = strrchr(name, '.');
-        if (extension)
-        {
-          const char *archive_exts[] = {
-            ".tar", ".tgz", ".gz", ".bz2", ".tbz2", ".xz", ".zst", ".z",
-            ".zip", ".zipx", ".rar", ".lzh", ".lha", ".lzo", ".lz4", ".arj",
-            ".dmg", ".hqx", ".cpt", ".7z", ".s7z",
-            NULL
-          };
-
-          for (int i = 0; archive_exts[i]; i++)
-          {
-            if (strcasecmp(extension, archive_exts[i]) == 0)
-            {
-              is_compressed = true;
-              break;
-            }
-          }
-        }
-
-        if (is_compressed)
-        {
-          logmsg("-- Ignoring compressed file ", name);
-          continue;
-        }
-
-        if (strcasecmp(extension, ".cue") == 0)
-        {
-          continue; // .cue will be handled with corresponding .bin
-        }
-
         // Check if the image should be loaded to microcontroller flash ROM drive
         bool is_romdrive = false;
+        const char *extension = strrchr(name, '.');
         if (extension && strcasecmp(extension, ".rom") == 0)
         {
           is_romdrive = true;
         }
-        else if (extension && strcasecmp(extension, ".rom_loaded") == 0)
-        {
-          // Already loaded ROM drive, ignore the image
-          continue;
-        }
+
+        // skip file if the name indicates it is not a valid image container
+        if (!is_romdrive && !scsiDiskFilenameValid(name)) continue;
 
         // Defaults for Hard Disks
         int id  = 1; // 0 and 3 are common in Macs for physical HD and CD, so avoid them.

+ 3 - 7
src/ZuluSCSI_cdrom.cpp

@@ -842,7 +842,8 @@ void cdromReinsertFirstImage(image_config_t &img)
     {
         // Multiple images for this drive, force restart from first one
         dbgmsg("---- Restarting from first CD-ROM image");
-        img.image_index = 9;
+        img.image_index = IMAGE_INDEX_MAX;
+        img.current_image[0] = '\0';
         cdromSwitchNextImage(img);
     }
     else if (img.ejected)
@@ -858,14 +859,9 @@ void cdromReinsertFirstImage(image_config_t &img)
 bool cdromSwitchNextImage(image_config_t &img)
 {
     // Check if we have a next image to load, so that drive is closed next time the host asks.
-    img.image_index++;
     char filename[MAX_FILE_PATH];
     int target_idx = img.scsiId & 7;
-    if (!scsiDiskGetImageNameFromConfig(img, filename, sizeof(filename)))
-    {
-        img.image_index = 0;
-        scsiDiskGetImageNameFromConfig(img, filename, sizeof(filename));
-    }
+    scsiDiskGetNextImageName(img, filename, sizeof(filename));
 
 #ifdef ENABLE_AUDIO_OUTPUT
     // if in progress for this device, terminate audio playback immediately (Annex C)

+ 3 - 0
src/ZuluSCSI_config.h

@@ -56,6 +56,9 @@
 #define HDIMG_BLK_POS 5                 // Position to embed block size numbers
 #define MAX_FILE_PATH 64                // Maximum file name length
 
+// Image definition options
+#define IMAGE_INDEX_MAX 9               // Maximum number of 'IMG0' style statements parsed
+
 // SCSI config
 #define NUM_SCSIID  8          // Maximum number of supported SCSI-IDs (The minimum is 0)
 #define NUM_SCSILUN 1          // Maximum number of LUNs supported     (Currently has to be 1)

+ 201 - 10
src/ZuluSCSI_disk.cpp

@@ -425,8 +425,11 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
 
         return true;
     }
-
-    return false;
+    else
+    {
+        logmsg("---- Failed to load image '", filename, "', ignoring");
+        return false;
+    }
 }
 
 static void checkDiskGeometryDivisible(image_config_t &img)
@@ -446,6 +449,43 @@ static void checkDiskGeometryDivisible(image_config_t &img)
     }
 }
 
+bool scsiDiskFilenameValid(const char* name)
+{
+    // Check file extension
+    const char *extension = strrchr(name, '.');
+    if (extension)
+    {
+        const char *ignore_exts[] = {
+            ".rom_loaded", ".cue",
+            NULL
+        };
+        const char *archive_exts[] = {
+            ".tar", ".tgz", ".gz", ".bz2", ".tbz2", ".xz", ".zst", ".z",
+            ".zip", ".zipx", ".rar", ".lzh", ".lha", ".lzo", ".lz4", ".arj",
+            ".dmg", ".hqx", ".cpt", ".7z", ".s7z",
+            NULL
+        };
+
+        for (int i = 0; ignore_exts[i]; i++)
+        {
+            if (strcasecmp(extension, ignore_exts[i]) == 0)
+            {
+                // ignore these without log message
+                return false;
+            }
+        }
+        for (int i = 0; archive_exts[i]; i++)
+        {
+            if (strcasecmp(extension, archive_exts[i]) == 0)
+            {
+                logmsg("-- Ignoring compressed file ", name);
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
 // Set target configuration to default values
 static void scsiDiskConfigDefaults(int target_idx)
 {
@@ -500,21 +540,171 @@ static void scsiDiskLoadConfig(int target_idx, const char *section)
     memset(tmp, 0, sizeof(tmp));
     ini_gets(section, "Serial", "", tmp, sizeof(tmp), CONFIGFILE);
     if (tmp[0]) memcpy(img.serial, tmp, sizeof(img.serial));
+
+    if (strlen(section) == 5 && strncmp(section, "SCSI", 4) == 0) // allow within target [SCSIx] blocks only
+    {
+        ini_gets(section, "ImgDir", "", tmp, sizeof(tmp), CONFIGFILE);
+        if (tmp[0])
+        {
+            logmsg("-- SCSI", target_idx, " using image directory \'", tmp, "'");
+            img.image_directory = true;
+        }
+    }
 }
 
-// Check if image file name is overridden in config
-bool scsiDiskGetImageNameFromConfig(image_config_t &img, char *buf, size_t buflen)
+// Finds filename with the lowest lexical order _after_ the given filename in
+// the given folder. If there is no file after the given one, or if there is
+// no current file, this will return the lowest filename encountered.
+static int findNextImageAfter(image_config_t &img,
+        const char* dirname, const char* filename,
+        char* buf, size_t buflen)
+{
+    FsFile dir;
+    if (dirname[0] == '\0')
+    {
+        logmsg("Image directory name invalid for ID", (img.scsiId & 7));
+        return 0;
+    }
+    if (!dir.open(dirname))
+    {
+        logmsg("Image directory '", dirname, "' couldn't be opened");
+    }
+    if (!dir.isDir())
+    {
+        logmsg("Can't find images in '", dirname, "', not a directory");
+        dir.close();
+        return 0;
+    }
+
+    char first_name[MAX_FILE_PATH] = {'\0'};
+    char candidate_name[MAX_FILE_PATH] = {'\0'};
+    FsFile file;
+    while (file.openNext(&dir, O_RDONLY))
+    {
+        if (file.isDir()) continue;
+        if (!file.getName(buf, MAX_FILE_PATH))
+        {
+            logmsg("Image directory '", dirname, "'had invalid file");
+            continue;
+        }
+        if (!scsiDiskFilenameValid(buf)) continue;
+
+        // keep track of the first item to allow wrapping
+        // without having to iterate again
+        if (first_name[0] == '\0' || strcasecmp(buf, first_name) < 0)
+        {
+            strncpy(first_name, buf, sizeof(first_name));
+        }
+
+        // discard if no selected name, or if candidate is before (or is) selected
+        if (filename[0] == '\0' || strcasecmp(buf, filename) <= 0) continue;
+
+        // if we got this far and the candidate is either 1) not set, or 2) is a
+        // lower item than what has been encountered thus far, it is the best choice
+        if (candidate_name[0] == '\0' || strcasecmp(buf, candidate_name) < 0)
+        {
+            strncpy(candidate_name, buf, sizeof(candidate_name));
+        }
+    }
+
+    if (candidate_name[0] != '\0')
+    {
+        img.image_index++;
+        strncpy(img.current_image, candidate_name, sizeof(img.current_image));
+        strncpy(buf, candidate_name, buflen);
+        return strlen(candidate_name);
+    }
+    else if (first_name[0] != '\0')
+    {
+        img.image_index = 0;
+        strncpy(img.current_image, first_name, sizeof(img.current_image));
+        strncpy(buf, first_name, buflen);
+        return strlen(first_name);
+    }
+    else
+    {
+        logmsg("Image directory '", dirname, "' was empty");
+        return 0;
+    }
+}
+
+int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
 {
     int target_idx = img.scsiId & 7;
 
     char section[6] = "SCSI0";
     section[4] = '0' + target_idx;
 
-    char key[5] = "IMG0";
-    key[3] = '0' + img.image_index;
+    // sanity check: is provided buffer is long enough to store a filename?
+    assert(buflen >= MAX_FILE_PATH);
+
+    if (img.image_directory)
+    {
+        // image directory was found during startup
+        char dirname[MAX_FILE_PATH];
+        char key[] = "ImgDir";
+        int dirlen = ini_gets(section, key, "", dirname, sizeof(dirname), CONFIGFILE);
+        if (!dirlen)
+        {
+            // If image_directory set but ImageDir is not, could be used to
+            // indicate an image directory configured via folder structure.
+            // Not implemented, so treat this as equivalent to missing ImageDir
+            return 0;
+        }
+
+        // find the next filename
+        char nextname[MAX_FILE_PATH];
+        int nextlen = findNextImageAfter(img, dirname, img.current_image, nextname, sizeof(nextname));
 
-    ini_gets(section, key, "", buf, buflen, CONFIGFILE);
-    return buf[0] != '\0';
+        if (nextlen == 0)
+        {
+            logmsg("Image directory was empty for ID", target_idx);
+            return 0;
+        }
+        else if (buflen < nextlen + dirlen + 2)
+        {
+            logmsg("Directory '", dirname, "' and file '", nextname, "' exceed allowed length");
+            return 0;
+        }
+        else
+        {
+            // construct a return value
+            strncpy(buf, dirname, buflen);
+            if (buf[strlen(buf) - 1] != '/') strcat(buf, "/");
+            strcat(buf, nextname);
+            return dirlen + nextlen;
+        }
+    }
+    else
+    {
+        img.image_index++;
+        if (img.image_index > IMAGE_INDEX_MAX)
+        {
+            img.image_index = 0;
+        }
+
+        char key[5] = "IMG0";
+        key[3] = '0' + img.image_index;
+
+        int ret = ini_gets(section, key, "", buf, buflen, CONFIGFILE);
+        if (buf[0] != '\0')
+        {
+            return ret;
+        }
+        else if (img.image_index > 0)
+        {
+            // there may be more than one image but we've ran out of new ones
+            // wrap back to the first image
+            img.image_index = IMAGE_INDEX_MAX;
+            return scsiDiskGetNextImageName(img, buf, buflen);
+        }
+        else
+        {
+            // images are not defined in config
+            img.image_index = IMAGE_INDEX_MAX;
+            return 0;
+        }
+    }
 }
 
 void scsiDiskLoadConfig(int target_idx)
@@ -534,10 +724,11 @@ void scsiDiskLoadConfig(int target_idx)
     // Check if we have image specified by name
     char filename[MAX_FILE_PATH];
     image_config_t &img = g_DiskImages[target_idx];
-    if (scsiDiskGetImageNameFromConfig(img, filename, sizeof(filename)))
+    img.image_index = IMAGE_INDEX_MAX;
+    if (scsiDiskGetNextImageName(img, filename, sizeof(filename)))
     {
         int blocksize = (img.deviceType == S2S_CFG_OPTICAL) ? 2048 : 512;
-        logmsg("-- Opening ", filename, " for id:", target_idx, ", specified in " CONFIGFILE);
+        logmsg("-- Opening '", filename, "' for id:", target_idx, ", specified in " CONFIGFILE);
         scsiDiskOpenHDDImage(target_idx, filename, target_idx, 0, blocksize);
     }
 }

+ 16 - 4
src/ZuluSCSI_disk.h

@@ -32,6 +32,7 @@
 #include <scsi2sd.h>
 #include <scsiPhy.h>
 #include "ImageBackingStore.h"
+#include "ZuluSCSI_config.h"
 
 extern "C" {
 #include <disk.h>
@@ -61,8 +62,13 @@ struct image_config_t: public S2S_TargetCfg
     // For tape drive emulation, current position in blocks
     uint32_t tape_pos;
 
+    // True if there is a subdirectory of images for this target
+    bool image_directory;
+    // the name of the currently mounted image in a dynamic image directory
+    char current_image[MAX_FILE_PATH];
     // Index of image, for when image on-the-fly switching is used for CD drives
-    int image_index;
+    // This is also used for dynamic directories to track how many images have been seen
+    uint8_t image_index;
 
     // Cue sheet file for CD-ROM images
     FsFile cuesheetfile;
@@ -93,6 +99,10 @@ void scsiDiskCloseSDCardImages();
 bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int scsi_lun, int blocksize, S2S_CFG_TYPE type = S2S_CFG_FIXED);
 void scsiDiskLoadConfig(int target_idx);
 
+// Checks if a filename extension is appropriate for further processing as a disk image.
+// The current implementation does not check the the filename prefix for validity.
+bool scsiDiskFilenameValid(const char* name);
+
 // Clear the ROM drive header from flash
 bool scsiDiskClearRomDrive();
 // Program ROM drive and rename image file
@@ -105,9 +115,11 @@ bool scsiDiskActivateRomDrive();
 // Returns true if there is at least one image active
 bool scsiDiskCheckAnyImagesConfigured();
 
-// Check if image file name is overridden in config,
-// including image index for multi-image CD-ROM emulation
-bool scsiDiskGetImageNameFromConfig(image_config_t &img, char *buf, size_t buflen);
+// Gets the next image filename for the target, if configured for multiple
+// images. As a side effect this advances image tracking to the next image.
+// Returns the length of the new image filename, or 0 if the target is not
+// configured for multiple images.
+int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen);
 
 // Get pointer to extended image configuration based on target idx
 image_config_t &scsiDiskGetImageConfig(int target_idx);