|
@@ -425,8 +425,11 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
|
|
|
|
|
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ logmsg("---- Failed to load image '", filename, "', ignoring");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void checkDiskGeometryDivisible(image_config_t &img)
|
|
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
|
|
// Set target configuration to default values
|
|
|
static void scsiDiskConfigDefaults(int target_idx)
|
|
static void scsiDiskConfigDefaults(int target_idx)
|
|
|
{
|
|
{
|
|
@@ -500,21 +540,171 @@ static void scsiDiskLoadConfig(int target_idx, const char *section)
|
|
|
memset(tmp, 0, sizeof(tmp));
|
|
memset(tmp, 0, sizeof(tmp));
|
|
|
ini_gets(section, "Serial", "", tmp, sizeof(tmp), CONFIGFILE);
|
|
ini_gets(section, "Serial", "", tmp, sizeof(tmp), CONFIGFILE);
|
|
|
if (tmp[0]) memcpy(img.serial, tmp, sizeof(img.serial));
|
|
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;
|
|
int target_idx = img.scsiId & 7;
|
|
|
|
|
|
|
|
char section[6] = "SCSI0";
|
|
char section[6] = "SCSI0";
|
|
|
section[4] = '0' + target_idx;
|
|
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)
|
|
void scsiDiskLoadConfig(int target_idx)
|
|
@@ -534,10 +724,11 @@ void scsiDiskLoadConfig(int target_idx)
|
|
|
// Check if we have image specified by name
|
|
// Check if we have image specified by name
|
|
|
char filename[MAX_FILE_PATH];
|
|
char filename[MAX_FILE_PATH];
|
|
|
image_config_t &img = g_DiskImages[target_idx];
|
|
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;
|
|
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);
|
|
scsiDiskOpenHDDImage(target_idx, filename, target_idx, 0, blocksize);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|