summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/zippath.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/util/zippath.cpp')
-rw-r--r--src/lib/util/zippath.cpp1101
1 files changed, 1101 insertions, 0 deletions
diff --git a/src/lib/util/zippath.cpp b/src/lib/util/zippath.cpp
new file mode 100644
index 00000000000..36c9470ed92
--- /dev/null
+++ b/src/lib/util/zippath.cpp
@@ -0,0 +1,1101 @@
+// license:BSD-3-Clause
+// copyright-holders:Nathan Woods
+/***************************************************************************
+
+ zippath.c
+
+ File/directory/path operations that work with ZIP files
+
+***************************************************************************/
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <new>
+#include <assert.h>
+#include "zippath.h"
+#include "unzip.h"
+#include "corestr.h"
+#include "osdcore.h"
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+/**
+ * @struct zippath_returned_directory
+ *
+ * @brief A zippath returned directory.
+ */
+
+struct zippath_returned_directory
+{
+ /** @brief The next. */
+ zippath_returned_directory *next;
+ /** @brief The name. */
+ std::string name;
+};
+
+/**
+ * @class zippath_directory
+ *
+ * @brief A zippath directory.
+ */
+
+class zippath_directory
+{
+public:
+ zippath_directory()
+ : returned_parent(false),
+ directory(NULL),
+ called_zip_first(false),
+ zipfile(NULL),
+ returned_dirlist(NULL) { }
+
+ /* common */
+ /** @brief true to returned parent. */
+ bool returned_parent;
+ /** @brief The returned entry. */
+ osd_directory_entry returned_entry;
+
+ /* specific to normal directories */
+ /** @brief Pathname of the directory. */
+ osd_directory *directory;
+
+ /* specific to ZIP directories */
+ /** @brief true to called zip first. */
+ bool called_zip_first;
+ /** @brief The zipfile. */
+ zip_file *zipfile;
+ /** @brief The zipprefix. */
+ std::string zipprefix;
+ /** @brief The returned dirlist. */
+ zippath_returned_directory *returned_dirlist;
+};
+
+
+/***************************************************************************
+ FUNCTION PROTOTYPES
+***************************************************************************/
+
+static const zip_file_header *zippath_find_sub_path(zip_file *zipfile, const char *subpath, osd_dir_entry_type *type);
+static int is_zip_file(const char *path);
+static int is_zip_file_separator(char c);
+static int is_7z_file(const char *path);
+
+
+/***************************************************************************
+ PATH OPERATIONS
+***************************************************************************/
+
+/**
+ * @fn int is_path_separator(char c)
+ *
+ * @brief ============================================================
+ * is_path_separator
+ * ============================================================.
+ *
+ * @param c The character.
+ *
+ * @return An int.
+ */
+
+int is_path_separator(char c)
+{
+ return (c == '/') || (c == '\\');
+}
+
+/*-------------------------------------------------
+ parse_parent_path - parses out the parent path
+-------------------------------------------------*/
+
+/**
+ * @fn static void parse_parent_path(const char *path, int *beginpos, int *endpos)
+ *
+ * @brief Parse parent path.
+ *
+ * @param path Full pathname of the file.
+ * @param [in,out] beginpos If non-null, the beginpos.
+ * @param [in,out] endpos If non-null, the endpos.
+ */
+
+static void parse_parent_path(const char *path, int *beginpos, int *endpos)
+{
+ int length = strlen(path);
+ int pos;
+
+ /* skip over trailing path separators */
+ pos = length - 1;
+ while((pos > 0) && is_path_separator(path[pos]))
+ pos--;
+
+ /* return endpos */
+ if (endpos != NULL)
+ *endpos = pos;
+
+ /* now skip until we find a path separator */
+ while((pos >= 0) && !is_path_separator(path[pos]))
+ pos--;
+
+ /* return beginpos */
+ if (beginpos != NULL)
+ *beginpos = pos;
+}
+
+
+
+/*-------------------------------------------------
+ zippath_parent - retrieves the parent directory
+-------------------------------------------------*/
+
+/**
+ * @fn std::string &zippath_parent(std::string &dst, const char *path)
+ *
+ * @brief Zippath parent.
+ *
+ * @param [in,out] dst Destination for the.
+ * @param path Full pathname of the file.
+ *
+ * @return A std::string&amp;
+ */
+
+std::string &zippath_parent(std::string &dst, const char *path)
+{
+ int pos;
+ parse_parent_path(path, &pos, NULL);
+
+ /* return the result */
+ if (pos >= 0) {
+ dst.assign(path, pos + 1);
+ }
+ else {
+ dst.clear();
+ }
+ return dst;
+}
+
+
+
+/*-------------------------------------------------
+ zippath_parent_basename - retrieves the parent
+ directory basename
+-------------------------------------------------*/
+
+/**
+ * @fn std::string &zippath_parent_basename(std::string &dst, const char *path)
+ *
+ * @brief Zippath parent basename.
+ *
+ * @param [in,out] dst Destination for the.
+ * @param path Full pathname of the file.
+ *
+ * @return A std::string&amp;
+ */
+
+std::string &zippath_parent_basename(std::string &dst, const char *path)
+{
+ int beginpos, endpos;
+ parse_parent_path(path, &beginpos, &endpos);
+ dst.copy((char*)(path + beginpos + 1), endpos - beginpos);
+ return dst;
+}
+
+
+
+/*-------------------------------------------------
+ zippath_combine - combines two paths
+-------------------------------------------------*/
+
+/**
+ * @fn std::string &zippath_combine(std::string &dst, const char *path1, const char *path2)
+ *
+ * @brief Zippath combine.
+ *
+ * @param [in,out] dst Destination for the.
+ * @param path1 The first path.
+ * @param path2 The second path.
+ *
+ * @return A std::string&amp;
+ */
+
+std::string &zippath_combine(std::string &dst, const char *path1, const char *path2)
+{
+ if (!strcmp(path2, "."))
+ {
+ dst.assign(path1);
+ }
+ else if (!strcmp(path2, ".."))
+ {
+ zippath_parent(dst, path1);
+ }
+ else if (osd_is_absolute_path(path2))
+ {
+ dst.assign(path2);
+ }
+ else if ((path1[0] != '\0') && !is_path_separator(path1[strlen(path1) - 1]))
+ {
+ dst.assign(path1).append(PATH_SEPARATOR).append(path2);
+ }
+ else
+ {
+ dst.assign(path1).append(path2);
+ }
+ return dst;
+}
+
+
+
+/***************************************************************************
+ FILE OPERATIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ file_error_from_zip_error - translates a
+ file_error to a zip_error
+-------------------------------------------------*/
+
+/**
+ * @fn static file_error file_error_from_zip_error(zip_error ziperr)
+ *
+ * @brief File error from zip error.
+ *
+ * @param ziperr The ziperr.
+ *
+ * @return A file_error.
+ */
+
+static file_error file_error_from_zip_error(zip_error ziperr)
+{
+ file_error filerr;
+ switch(ziperr)
+ {
+ case ZIPERR_NONE:
+ filerr = FILERR_NONE;
+ break;
+ case ZIPERR_OUT_OF_MEMORY:
+ filerr = FILERR_OUT_OF_MEMORY;
+ break;
+ case ZIPERR_BAD_SIGNATURE:
+ case ZIPERR_DECOMPRESS_ERROR:
+ case ZIPERR_FILE_TRUNCATED:
+ case ZIPERR_FILE_CORRUPT:
+ case ZIPERR_UNSUPPORTED:
+ case ZIPERR_FILE_ERROR:
+ filerr = FILERR_INVALID_DATA;
+ break;
+ case ZIPERR_BUFFER_TOO_SMALL:
+ default:
+ filerr = FILERR_FAILURE;
+ break;
+ }
+ return filerr;
+}
+
+
+/*-------------------------------------------------
+ create_core_file_from_zip - creates a core_file
+ from a zip file entry
+-------------------------------------------------*/
+
+/**
+ * @fn static file_error create_core_file_from_zip(zip_file *zip, const zip_file_header *header, core_file *&file)
+ *
+ * @brief Creates core file from zip.
+ *
+ * @param [in,out] zip If non-null, the zip.
+ * @param header The header.
+ * @param [in,out] file [in,out] If non-null, the file.
+ *
+ * @return The new core file from zip.
+ */
+
+static file_error create_core_file_from_zip(zip_file *zip, const zip_file_header *header, core_file *&file)
+{
+ file_error filerr;
+ zip_error ziperr;
+ void *ptr;
+
+ ptr = malloc(header->uncompressed_length);
+ if (ptr == NULL)
+ {
+ filerr = FILERR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ ziperr = zip_file_decompress(zip, ptr, header->uncompressed_length);
+ if (ziperr != ZIPERR_NONE)
+ {
+ filerr = file_error_from_zip_error(ziperr);
+ goto done;
+ }
+
+ filerr = core_fopen_ram_copy(ptr, header->uncompressed_length, OPEN_FLAG_READ, &file);
+ if (filerr != FILERR_NONE)
+ goto done;
+
+done:
+ if (ptr != NULL)
+ free(ptr);
+ return filerr;
+}
+
+
+/*-------------------------------------------------
+ zippath_fopen - opens a zip path file
+-------------------------------------------------*/
+
+/**
+ * @fn file_error zippath_fopen(const char *filename, UINT32 openflags, core_file *&file, std::string &revised_path)
+ *
+ * @brief Zippath fopen.
+ *
+ * @param filename Filename of the file.
+ * @param openflags The openflags.
+ * @param [in,out] file [in,out] If non-null, the file.
+ * @param [in,out] revised_path Full pathname of the revised file.
+ *
+ * @return A file_error.
+ */
+
+file_error zippath_fopen(const char *filename, UINT32 openflags, core_file *&file, std::string &revised_path)
+{
+ file_error filerr = FILERR_NOT_FOUND;
+ zip_error ziperr;
+ zip_file *zip = NULL;
+ const zip_file_header *header;
+ osd_dir_entry_type entry_type;
+ char *alloc_fullpath = NULL;
+ int len;
+
+ /* first, set up the two types of paths */
+ std::string mainpath(filename);
+ std::string subpath;
+ file = NULL;
+
+ /* loop through */
+ while((file == NULL) && (mainpath.length() > 0)
+ && ((openflags == OPEN_FLAG_READ) || (subpath.length() == 0)))
+ {
+ /* is the mainpath a ZIP path? */
+ if (is_zip_file(mainpath.c_str()))
+ {
+ /* this file might be a zip file - lets take a look */
+ ziperr = zip_file_open(mainpath.c_str(), &zip);
+ if (ziperr == ZIPERR_NONE)
+ {
+ /* it is a zip file - error if we're not opening for reading */
+ if (openflags != OPEN_FLAG_READ)
+ {
+ filerr = FILERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ if (subpath.length() > 0)
+ header = zippath_find_sub_path(zip, subpath.c_str(), &entry_type);
+ else
+ header = zip_file_first_file(zip);
+
+ if (header == NULL)
+ {
+ filerr = FILERR_NOT_FOUND;
+ goto done;
+ }
+
+ /* attempt to read the file */
+ filerr = create_core_file_from_zip(zip, header, file);
+ if (filerr != FILERR_NONE)
+ goto done;
+
+ /* update subpath, if appropriate */
+ if (subpath.length() == 0)
+ subpath.assign(header->filename);
+
+ /* we're done */
+ goto done;
+ }
+ }
+ else if (is_7z_file(mainpath.c_str()))
+ {
+ filerr = FILERR_INVALID_DATA;
+ goto done;
+ }
+
+ if (subpath.length() == 0)
+ filerr = core_fopen(filename, openflags, &file);
+ else
+ filerr = FILERR_NOT_FOUND;
+
+ /* if we errored, then go up a directory */
+ if (filerr != FILERR_NONE)
+ {
+ /* go up a directory */
+ std::string temp;
+ zippath_parent(temp, mainpath.c_str());
+
+ /* append to the sub path */
+ if (subpath.length() > 0)
+ {
+ std::string temp2;
+ mainpath = mainpath.substr(temp.length());
+ temp2.assign(mainpath).append(PATH_SEPARATOR).append(subpath);
+ subpath.assign(temp2);
+ }
+ else
+ {
+ mainpath = mainpath.substr(temp.length());
+ subpath.assign(mainpath);
+ }
+ /* get the new main path, truncating path separators */
+ len = temp.length();
+ while (len > 0 && is_zip_file_separator(temp[len - 1]))
+ len--;
+ temp = temp.substr(0, len);
+ mainpath.assign(temp);
+ }
+ }
+
+done:
+ /* store the revised path */
+ revised_path.clear();
+ if (filerr == FILERR_NONE)
+ {
+ /* cannonicalize mainpath */
+ filerr = osd_get_full_path(&alloc_fullpath, mainpath.c_str());
+ if (filerr == FILERR_NONE)
+ {
+ if (subpath.length() > 0)
+ revised_path.assign(alloc_fullpath).append(PATH_SEPARATOR).append(subpath);
+ else
+ revised_path.assign(alloc_fullpath);
+ }
+ }
+
+ if (zip != NULL)
+ zip_file_close(zip);
+ if (alloc_fullpath != NULL)
+ osd_free(alloc_fullpath);
+ return filerr;
+}
+
+
+/***************************************************************************
+ DIRECTORY OPERATIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ is_root - tests to see if this path is the root
+-------------------------------------------------*/
+
+/**
+ * @fn static int is_root(const char *path)
+ *
+ * @brief Is root.
+ *
+ * @param path Full pathname of the file.
+ *
+ * @return An int.
+ */
+
+static int is_root(const char *path)
+{
+ int i = 0;
+
+ /* skip drive letter */
+ if (isalpha(path[i]) && (path[i + 1] == ':'))
+ i += 2;
+
+ /* skip path separators */
+ while (is_path_separator(path[i]))
+ i++;
+
+ return path[i] == '\0';
+}
+
+
+
+/*-------------------------------------------------
+ is_7z_file - tests to see if this file is a
+ 7-zip file
+-------------------------------------------------*/
+
+/**
+ * @fn static int is_7z_file(const char *path)
+ *
+ * @brief Is 7z file.
+ *
+ * @param path Full pathname of the file.
+ *
+ * @return An int.
+ */
+
+static int is_7z_file(const char *path)
+{
+ const char *s = strrchr(path, '.');
+ return (s != NULL) && !core_stricmp(s, ".7z");
+}
+
+
+/*-------------------------------------------------
+ is_zip_file - tests to see if this file is a
+ ZIP file
+-------------------------------------------------*/
+
+/**
+ * @fn static int is_zip_file(const char *path)
+ *
+ * @brief Is zip file.
+ *
+ * @param path Full pathname of the file.
+ *
+ * @return An int.
+ */
+
+static int is_zip_file(const char *path)
+{
+ const char *s = strrchr(path, '.');
+ return (s != NULL) && !core_stricmp(s, ".zip");
+}
+
+
+
+/*-------------------------------------------------
+ is_zip_file_separator - returns whether this
+ character is a path separator within a ZIP file
+-------------------------------------------------*/
+
+/**
+ * @fn static int is_zip_file_separator(char c)
+ *
+ * @brief Is zip file separator.
+ *
+ * @param c The character.
+ *
+ * @return An int.
+ */
+
+static int is_zip_file_separator(char c)
+{
+ return (c == '/') || (c == '\\');
+}
+
+
+
+/*-------------------------------------------------
+ is_zip_path_separator - returns whether this
+ character is a path separator within a ZIP path
+-------------------------------------------------*/
+
+/**
+ * @fn static int is_zip_path_separator(char c)
+ *
+ * @brief Is zip path separator.
+ *
+ * @param c The character.
+ *
+ * @return An int.
+ */
+
+static int is_zip_path_separator(char c)
+{
+ return is_zip_file_separator(c) || is_path_separator(c);
+}
+
+
+
+/*-------------------------------------------------
+ next_path_char - lexes out the next path
+ character, normalizing separators as '/'
+-------------------------------------------------*/
+
+/**
+ * @fn static char next_path_char(const char *s, int *pos)
+ *
+ * @brief Next path character.
+ *
+ * @param s The const char * to process.
+ * @param [in,out] pos If non-null, the position.
+ *
+ * @return A char.
+ */
+
+static char next_path_char(const char *s, int *pos)
+{
+ char result;
+
+ /* skip over any initial separators */
+ if (*pos == 0)
+ {
+ while(is_zip_file_separator(s[*pos]))
+ (*pos)++;
+ }
+
+ /* are we at a path separator? */
+ if (is_zip_file_separator(s[*pos]))
+ {
+ /* skip over path separators */
+ while(is_zip_file_separator(s[*pos]))
+ (*pos)++;
+
+ /* normalize as '/' */
+ result = '/';
+ }
+ else if (s[*pos] != '\0')
+ {
+ /* return character */
+ result = tolower(s[(*pos)++]);
+ }
+ else
+ {
+ /* return NUL */
+ result = '\0';
+ }
+ return result;
+}
+
+
+
+
+/*-------------------------------------------------
+ zippath_find_sub_path - attempts to identify the
+ type of a sub path in a zip file
+-------------------------------------------------*/
+
+/**
+ * @fn static const zip_file_header *zippath_find_sub_path(zip_file *zipfile, const char *subpath, osd_dir_entry_type *type)
+ *
+ * @brief Zippath find sub path.
+ *
+ * @param [in,out] zipfile If non-null, the zipfile.
+ * @param subpath The subpath.
+ * @param [in,out] type If non-null, the type.
+ *
+ * @return null if it fails, else a zip_file_header*.
+ */
+
+static const zip_file_header *zippath_find_sub_path(zip_file *zipfile, const char *subpath, osd_dir_entry_type *type)
+{
+ int i, j;
+ char c1, c2, last_char;
+ const zip_file_header *header;
+
+ for (header = zip_file_first_file(zipfile); header != NULL; header = zip_file_next_file(zipfile))
+ {
+ /* special case */
+ if (subpath == NULL)
+ {
+ if (type != NULL)
+ *type = ENTTYPE_FILE;
+ return header;
+ }
+
+ i = 0;
+ j = 0;
+ last_char = '/';
+ while(((c1 = next_path_char(header->filename, &i)) == (c2 = next_path_char(subpath, &j))) &&
+ ( c1 != '\0' && c2 != '\0' ))
+ last_char = c2;
+
+
+ if (c2 == '\0')
+ {
+ if (c1 == '\0')
+ {
+ if (type != NULL)
+ *type = ENTTYPE_FILE;
+ return header;
+ }
+ else if ((last_char == '/') || (c1 == '/'))
+ {
+ if (type != NULL)
+ *type = ENTTYPE_DIR;
+ return header;
+ }
+ }
+ }
+
+ if (type != NULL)
+ *type = ENTTYPE_NONE;
+ return NULL;
+}
+
+
+
+/*-------------------------------------------------
+ zippath_resolve - separates a ZIP path out into
+ true path and ZIP entry components
+-------------------------------------------------*/
+
+/**
+ * @fn static file_error zippath_resolve(const char *path, osd_dir_entry_type &entry_type, zip_file *&zipfile, std::string &newpath)
+ *
+ * @brief Zippath resolve.
+ *
+ * @param path Full pathname of the file.
+ * @param [in,out] entry_type Type of the entry.
+ * @param [in,out] zipfile [in,out] If non-null, the zipfile.
+ * @param [in,out] newpath The newpath.
+ *
+ * @return A file_error.
+ */
+
+static file_error zippath_resolve(const char *path, osd_dir_entry_type &entry_type, zip_file *&zipfile, std::string &newpath)
+{
+ file_error err;
+ osd_directory_entry *current_entry = NULL;
+ osd_dir_entry_type current_entry_type;
+ int went_up = FALSE;
+ int i;
+
+ newpath.clear();
+
+ /* be conservative */
+ entry_type = ENTTYPE_NONE;
+ zipfile = NULL;
+
+ std::string apath(path);
+ std::string apath_trimmed;
+ do
+ {
+ /* trim the path of trailing path separators */
+ i = apath.length();
+ while (i > 1 && is_path_separator(apath[i - 1]))
+ i--;
+ apath = apath.substr(0, i);
+ apath_trimmed.assign(apath);
+
+ /* stat the path */
+ current_entry = osd_stat(apath_trimmed.c_str());
+
+ /* did we find anything? */
+ if (current_entry != NULL)
+ {
+ /* get the entry type and free the stat entry */
+ current_entry_type = current_entry->type;
+ osd_free(current_entry);
+ current_entry = NULL;
+ }
+ else
+ {
+ /* if we have not found the file or directory, go up */
+ current_entry_type = ENTTYPE_NONE;
+ went_up = TRUE;
+ std::string parent;
+ apath.assign(zippath_parent(parent, apath.c_str()));
+ }
+ }
+ while (current_entry_type == ENTTYPE_NONE && !is_root(apath.c_str()));
+
+ /* if we did not find anything, then error out */
+ if (current_entry_type == ENTTYPE_NONE)
+ {
+ err = FILERR_NOT_FOUND;
+ goto done;
+ }
+
+ /* is this file a ZIP file? */
+ if ((current_entry_type == ENTTYPE_FILE) && is_zip_file(apath_trimmed.c_str())
+ && (zip_file_open(apath_trimmed.c_str(), &zipfile) == ZIPERR_NONE))
+ {
+ i = strlen(path + apath.length());
+ while (i > 0 && is_zip_path_separator(path[apath.length() + i - 1]))
+ i--;
+ newpath.assign(path + apath.length(), i);
+
+ /* this was a true ZIP path - attempt to identify the type of path */
+ zippath_find_sub_path(zipfile, newpath.c_str(), &current_entry_type);
+ if (current_entry_type == ENTTYPE_NONE)
+ {
+ err = FILERR_NOT_FOUND;
+ goto done;
+ }
+ }
+ else
+ {
+ /* this was a normal path */
+ if (went_up)
+ {
+ err = FILERR_NOT_FOUND;
+ goto done;
+ }
+ newpath.assign(path);
+ }
+
+ /* success! */
+ entry_type = current_entry_type;
+ err = FILERR_NONE;
+
+done:
+ return err;
+}
+
+
+/*-------------------------------------------------
+ zippath_opendir - opens a directory
+-------------------------------------------------*/
+
+/**
+ * @fn file_error zippath_opendir(const char *path, zippath_directory **directory)
+ *
+ * @brief Zippath opendir.
+ *
+ * @param path Full pathname of the file.
+ * @param [in,out] directory If non-null, pathname of the directory.
+ *
+ * @return A file_error.
+ */
+
+file_error zippath_opendir(const char *path, zippath_directory **directory)
+{
+ file_error err;
+
+ /* allocate a directory */
+ zippath_directory *result = NULL;
+ try
+ {
+ result = new zippath_directory;
+ }
+ catch (std::bad_alloc &)
+ {
+ err = FILERR_OUT_OF_MEMORY;
+ goto done;
+ }
+ /* resolve the path */
+ osd_dir_entry_type entry_type;
+ err = zippath_resolve(path, entry_type, result->zipfile, result->zipprefix);
+ if (err != FILERR_NONE)
+ goto done;
+
+ /* we have to be a directory */
+ if (entry_type != ENTTYPE_DIR)
+ {
+ err = FILERR_NOT_FOUND;
+ goto done;
+ }
+
+ /* was the result a ZIP? */
+ if (result->zipfile == NULL)
+ {
+ /* a conventional directory */
+ result->directory = osd_opendir(path);
+ if (result->directory == NULL)
+ {
+ err = FILERR_FAILURE;
+ goto done;
+ }
+
+ /* is this path the root? if so, pretend we've already returned the parent */
+ if (is_root(path))
+ result->returned_parent = true;
+ }
+
+done:
+ if ((directory == NULL || err != FILERR_NONE) && result != NULL)
+ {
+ zippath_closedir(result);
+ result = NULL;
+ }
+ if (directory != NULL)
+ *directory = result;
+ return err;
+}
+
+
+/*-------------------------------------------------
+ zippath_closedir - closes a directory
+-------------------------------------------------*/
+
+/**
+ * @fn void zippath_closedir(zippath_directory *directory)
+ *
+ * @brief Zippath closedir.
+ *
+ * @param [in,out] directory If non-null, pathname of the directory.
+ */
+
+void zippath_closedir(zippath_directory *directory)
+{
+ if (directory->directory != NULL)
+ osd_closedir(directory->directory);
+
+ if (directory->zipfile != NULL)
+ zip_file_close(directory->zipfile);
+
+ while (directory->returned_dirlist != NULL)
+ {
+ zippath_returned_directory *dirlist = directory->returned_dirlist;
+ directory->returned_dirlist = directory->returned_dirlist->next;
+ delete dirlist;
+ }
+
+ delete directory;
+}
+
+
+/*-------------------------------------------------
+ get_relative_path - checks to see if a specified
+ header is in the zippath_directory, and if so
+ returns the relative path
+-------------------------------------------------*/
+
+/**
+ * @fn static const char *get_relative_path(zippath_directory *directory, const zip_file_header *header)
+ *
+ * @brief Gets relative path.
+ *
+ * @param [in,out] directory If non-null, pathname of the directory.
+ * @param header The header.
+ *
+ * @return null if it fails, else the relative path.
+ */
+
+static const char *get_relative_path(zippath_directory *directory, const zip_file_header *header)
+{
+ const char *result = NULL;
+ int len = directory->zipprefix.length();
+
+ if ((len <= strlen(header->filename))
+ && !strncmp(directory->zipprefix.c_str(), header->filename, len))
+ {
+ result = &header->filename[len];
+ while(is_zip_file_separator(*result))
+ result++;
+ }
+
+ return result;
+}
+
+
+/*-------------------------------------------------
+ zippath_readdir - reads a directory
+-------------------------------------------------*/
+
+/**
+ * @fn const osd_directory_entry *zippath_readdir(zippath_directory *directory)
+ *
+ * @brief Zippath readdir.
+ *
+ * @param [in,out] directory If non-null, pathname of the directory.
+ *
+ * @return null if it fails, else an osd_directory_entry*.
+ */
+
+const osd_directory_entry *zippath_readdir(zippath_directory *directory)
+{
+ const osd_directory_entry *result = NULL;
+ const zip_file_header *header;
+ const char *relpath;
+ const char *separator;
+ const char *s;
+ zippath_returned_directory *rdent;
+
+ if (!directory->returned_parent)
+ {
+ /* first thing's first - return parent directory */
+ directory->returned_parent = true;
+ memset(&directory->returned_entry, 0, sizeof(directory->returned_entry));
+ directory->returned_entry.name = "..";
+ directory->returned_entry.type = ENTTYPE_DIR;
+ result = &directory->returned_entry;
+ }
+ else if (directory->directory != NULL)
+ {
+ /* a normal directory read */
+ do
+ {
+ result = osd_readdir(directory->directory);
+ }
+ while((result != NULL) && (!strcmp(result->name, ".") || !strcmp(result->name, "..")));
+
+ /* special case - is this entry a ZIP file? if so we need to return it as a "directory" */
+ if ((result != NULL) && is_zip_file(result->name))
+ {
+ /* copy; but change the entry type */
+ directory->returned_entry = *result;
+ directory->returned_entry.type = ENTTYPE_DIR;
+ result = &directory->returned_entry;
+ }
+ }
+ else if (directory->zipfile != NULL)
+ {
+ do
+ {
+ /* a zip file read */
+ do
+ {
+ if (!directory->called_zip_first)
+ header = zip_file_first_file(directory->zipfile);
+ else
+ header = zip_file_next_file(directory->zipfile);
+ directory->called_zip_first = true;
+ relpath = NULL;
+ }
+ while((header != NULL) && ((relpath = get_relative_path(directory, header)) == NULL));
+
+ if (relpath != NULL)
+ {
+ /* we've found a ZIP entry; but this may be an entry deep within the target directory */
+ for (s = relpath; *s && !is_zip_file_separator(*s); s++)
+ ;
+ separator = *s ? s : NULL;
+
+ if (separator != NULL)
+ {
+ /* a nested entry; loop through returned_dirlist to see if we've returned the parent directory */
+ for (rdent = directory->returned_dirlist; rdent != NULL; rdent = rdent->next)
+ {
+ if (!core_strnicmp(rdent->name.c_str(), relpath, separator - relpath))
+ break;
+ }
+
+ if (rdent == NULL)
+ {
+ /* we've found a new directory; add this to returned_dirlist */
+ rdent = new zippath_returned_directory;
+ rdent->next = directory->returned_dirlist;
+ rdent->name.assign(relpath, separator - relpath);
+ directory->returned_dirlist = rdent;
+
+ /* ...and return it */
+ memset(&directory->returned_entry, 0, sizeof(directory->returned_entry));
+ directory->returned_entry.name = rdent->name.c_str();
+ directory->returned_entry.type = ENTTYPE_DIR;
+ result = &directory->returned_entry;
+ }
+ }
+ else
+ {
+ /* a real file */
+ memset(&directory->returned_entry, 0, sizeof(directory->returned_entry));
+ directory->returned_entry.name = relpath;
+ directory->returned_entry.type = ENTTYPE_FILE;
+ directory->returned_entry.size = header->uncompressed_length;
+ result = &directory->returned_entry;
+ }
+ }
+ }
+ while((relpath != NULL) && (result == NULL));
+ }
+ return result;
+}
+
+
+
+/*-------------------------------------------------
+ zippath_is_zip - returns TRUE if this path is
+ a ZIP path or FALSE if not
+-------------------------------------------------*/
+
+/**
+ * @fn int zippath_is_zip(zippath_directory *directory)
+ *
+ * @brief Zippath is zip.
+ *
+ * @param [in,out] directory If non-null, pathname of the directory.
+ *
+ * @return An int.
+ */
+
+int zippath_is_zip(zippath_directory *directory)
+{
+ return directory->zipfile != NULL;
+}