/* OpenCP Module Player
 * copyright (c) 2020-'26 Stian Skjelstad <stian.skjelstad@gmail.com>
 *
 * Code to provide Windows filesystem into the virtual drives A: -> Z:
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "config.h"
#include <assert.h>
#include <ctype.h>
#include <errhandlingapi.h>
#include <fileapi.h>
#include <handleapi.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "types.h"
#include "boot/psetting.h"
#include "filesel/dirdb.h"
#include "filesel/filesystem.h"
#include "filesel/filesystem-drive.h"
#include "filesel/filesystem-setup.h"
#include "filesel/filesystem-windows.h"
#include "filesel/pfilesel.h"
#include "stuff/compat.h"
#include "stuff/utf-16.h"

struct windows_ocpdir_t
{
	struct ocpdir_t head;
};

struct windows_ocpfile_t
{
	struct ocpfile_t head;

	uint64_t filesize;
};

struct windows_ocpfilehandle_t
{
	struct ocpfilehandle_t head;

	struct windows_ocpfile_t *owner;

	HANDLE fd;
	int eof;
	int error;
	uint64_t pos;
};

       struct dmDrive  *dmDriveLetters[26]       = {0};
       char             dmLastActiveDriveLetter  = 0;
static struct ocpdir_t *dmDriveRoots[26]         = {0};

static struct ocpfile_t *windows_file_steal (struct ocpdir_t *parent, const uint32_t dirdb_node, uint64_t filesize);

static struct ocpdir_t *windows_dir_steal (struct ocpdir_t *parent, const uint32_t dirdb_node);

static void windows_dir_ref (struct ocpdir_t *_s)
{
	struct windows_ocpdir_t *s = (struct windows_ocpdir_t *)_s;
	s->head.refcount++;
}

static void windows_dir_unref (struct ocpdir_t *_s)
{
	struct windows_ocpdir_t *s = (struct windows_ocpdir_t *)_s;
	s->head.refcount--;
	if (s->head.refcount <= 0)
	{
		if (s->head.parent)
		{
			s->head.parent->unref (s->head.parent);
			s->head.parent = 0;
		}
		dirdbUnref (s->head.dirdb_ref, dirdb_use_dir);
		free (s);
	}
}

struct windows_ocpdirhandle_t
{
	void(*callback_file)(void *token, struct ocpfile_t *);
	void(*callback_dir )(void *token, struct ocpdir_t *);
	void *token;

	struct windows_ocpdir_t *owner;

	int EndOfList;
	HANDLE FindHandle;
	WIN32_FIND_DATAW FindData;
};

static ocpdirhandle_pt windows_dir_readdir_start (struct ocpdir_t *_s,
                                                  void(*callback_file)(void *token, struct ocpfile_t *),
                                                  void(*callback_dir )(void *token, struct ocpdir_t *),
                                                  void *token)
{
	struct windows_ocpdir_t *s = (struct windows_ocpdir_t *)_s;
	struct windows_ocpdirhandle_t *r;
	char *path;
	uint16_t *wpath;

	dirdbGetFullname_malloc (s->head.dirdb_ref, &path, DIRDB_FULLNAME_ENDSLASH | DIRDB_FULLNAME_BACKSLASH);
	if (!path)
	{
		fprintf (stderr, "[filesystem windows readdir_start]: dirdbGetFullname_malloc () failed #1\n");
		return 0;
	}

	wpath = utf8_to_utf16_LFN (path, 1);
	if (!wpath)
	{
		fprintf (stderr, "[filesystem windows readdir_start] malloc() failed #3\n");
		free (path);
		return 0;
	}

	r = calloc (sizeof (*r), 1);
	if (!r)
	{
		fprintf (stderr, "[filesystem windows readdir_start] malloc() failed #2\n");
		free (path);
		free (wpath);
		return 0;
	}

	r->callback_file = callback_file;
	r->callback_dir  = callback_dir;
	r->token         = token;

	r->FindHandle = FindFirstFileW (wpath, &r->FindData);
	free (wpath);
	wpath = 0;
	if (r->FindHandle == INVALID_HANDLE_VALUE)
	{ /* no files found, invalid path */
		char *lpMsgBuf = NULL;
		if (FormatMessage (
			FORMAT_MESSAGE_ALLOCATE_BUFFER |
			FORMAT_MESSAGE_FROM_SYSTEM     |
			FORMAT_MESSAGE_IGNORE_INSERTS,             /* dwFlags */
			NULL,                                      /* lpSource */
			GetLastError (),                           /* dwMessageId */
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* dwLanguageId */
			(LPSTR) &lpMsgBuf,                         /* lpBuffer */
			0,                                         /* nSize */
			NULL                                       /* Arguments */
		))
		{
			fprintf (stderr, "[filesystem] FindFirstFileW(L\"%s\"): %s\n", path, lpMsgBuf);
			LocalFree (lpMsgBuf);
		}
		free (path);
		free (r);
		return 0;
	}
	assert (r->FindHandle);
	free (path);

	r->owner = s;
	s->head.ref (&s->head);

	return r;
}

static int windows_dir_readdir_iterate (ocpdirhandle_pt h)
{
	struct windows_ocpdirhandle_t *r = (struct windows_ocpdirhandle_t *)h;
	struct windows_ocpdir_t *s = r->owner;
	char *cFileName;

	if (!r->FindHandle)
	{
		return 0;
	}
	if (r->EndOfList)
	{
		return 0;
	}
	cFileName = utf16_to_utf8 (r->FindData.cFileName);
	if (cFileName)
	{
		if (r->FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			if (strcmp (cFileName, ".") && strcmp (cFileName, ".."))
			{
				struct ocpdir_t *n = windows_dir_steal (&s->head, dirdbFindAndRef (s->head.dirdb_ref, cFileName, dirdb_use_dir));
				r->callback_dir (r->token, n);
				n->unref (n);
			}
		} else {
			struct ocpfile_t *n = windows_file_steal (&s->head, dirdbFindAndRef (s->head.dirdb_ref, cFileName, dirdb_use_file), ((uint64_t)(r->FindData.nFileSizeHigh) << 32) | r->FindData.nFileSizeLow);
			r->callback_file (r->token, n);
			n->unref (n);
		}
		free (cFileName);
		cFileName = 0;
	}
	if (!FindNextFileW (r->FindHandle, &r->FindData))
	{
		r->EndOfList = 1;
		return 0;
	}
	return 1;
}

static void windows_dir_readdir_cancel (ocpdirhandle_pt h)
{
	struct windows_ocpdirhandle_t *r = (struct windows_ocpdirhandle_t *)h;
	struct windows_ocpdir_t *s = r->owner;

	if (r->FindHandle)
	{
		FindClose (r->FindHandle);
		r->FindHandle = 0;
	}
	s->head.unref (&s->head);
	r->owner = 0;
	free (r);
}

static void windows_filehandle_ref (struct ocpfilehandle_t *_s)
{
	struct windows_ocpfilehandle_t *s = (struct windows_ocpfilehandle_t *)_s;
	s->head.refcount++;
}

static void windows_filehandle_unref (struct ocpfilehandle_t *_s)
{
	struct windows_ocpfilehandle_t *s = (struct windows_ocpfilehandle_t *)_s;
	s->head.refcount--;
	if (s->head.refcount <= 0)
	{
		if (s->fd)
		{
			CloseHandle (s->fd);
			s->fd = 0;
		}
		dirdbUnref (s->head.dirdb_ref, dirdb_use_filehandle);

		s->owner->head.unref(&s->owner->head);
		s->owner = 0;

		free (s);
	}
}

static int windows_filehandle_seek_set (struct ocpfilehandle_t *_s, int64_t pos)
{
	struct windows_ocpfilehandle_t *s = (struct windows_ocpfilehandle_t *)_s;
	LARGE_INTEGER request, reply;

	request.QuadPart = pos;
	reply.QuadPart = 0;

	if (!SetFilePointerEx (s->fd, request, &reply, 0 /* FILE_BEGIN */))
	{
		s->error = 1;
		s->eof = 1;
		return -1;
	} else {
		s->pos = reply.QuadPart;
	}

	s->error = 0;
	s->eof = (reply.QuadPart >= s->owner->filesize);

	return 0;
}

static uint64_t windows_filehandle_getpos (struct ocpfilehandle_t *_s)
{
	struct windows_ocpfilehandle_t *s = (struct windows_ocpfilehandle_t *)_s;

	return s->pos;
}

static int windows_filehandle_eof (struct ocpfilehandle_t *_s)
{
	struct windows_ocpfilehandle_t *s = (struct windows_ocpfilehandle_t *)_s;

	return s->eof;
}

static int windows_filehandle_error (struct ocpfilehandle_t *_s)
{
	struct windows_ocpfilehandle_t *s = (struct windows_ocpfilehandle_t *)_s;

	return s->error;
}

static uint64_t windows_filehandle_filesize (struct ocpfilehandle_t *_s)
{
	struct windows_ocpfilehandle_t *s = (struct windows_ocpfilehandle_t *)_s;

	return s->owner->filesize;
}

static int windows_filehandle_filesize_ready (struct ocpfilehandle_t *_s)
{
	return 1;
}

static int windows_filehandle_read (struct ocpfilehandle_t *_s, void *dst, int len)
{
	struct windows_ocpfilehandle_t *s = (struct windows_ocpfilehandle_t *)_s;
	DWORD bytesread = 0;

	if (!ReadFile (s->fd, dst, len, &bytesread, 0))
	{
		s->eof = 1;
		s->error = 1;
		return 0;
	}
	s->pos += bytesread;
	s->eof = (s->pos >= s->owner->filesize);

	return bytesread;
}

static void windows_file_ref (struct ocpfile_t *_s)
{
	struct windows_ocpfile_t *s = (struct windows_ocpfile_t *)_s;
	s->head.refcount++;
}

static void windows_file_unref (struct ocpfile_t *_s)
{
	struct windows_ocpfile_t *s = (struct windows_ocpfile_t *)_s;
	s->head.refcount--;
	if (!s->head.refcount)
	{
		dirdbUnref (s->head.dirdb_ref, dirdb_use_file);
		s->head.parent->unref (s->head.parent);
		s->head.parent = 0;
		free (s);
	}
}

static struct ocpfilehandle_t *windows_file_open (struct ocpfile_t *_s)
{
	struct windows_ocpfile_t *s = (struct windows_ocpfile_t *)_s;
	char *path;
	uint16_t *wpath;
	HANDLE fd;
	struct windows_ocpfilehandle_t *r;

	dirdbGetFullname_malloc (s->head.dirdb_ref, &path, DIRDB_FULLNAME_BACKSLASH);
	if (!path) return 0;
	wpath = utf8_to_utf16_LFN (path, 0);
	if (!wpath)
	{
		free (path);
		return 0;
	}

	fd = CreateFileW (wpath,                   /* lpFileName */
	                 GENERIC_READ,          /* dwDesiredAccess */
	                 FILE_SHARE_READ,       /* dwShareMode */
	                 0,                     /* lpSecurityAttributes */
	                 OPEN_EXISTING,         /* dwCreationDisposition */
	                 FILE_ATTRIBUTE_NORMAL, /* dwFlagsAndAttributes */
	                 0);                    /* hTemplateFile */
	if (fd == INVALID_HANDLE_VALUE)
	{
		char *lpMsgBuf = NULL;
		if (FormatMessage (
			FORMAT_MESSAGE_ALLOCATE_BUFFER |
			FORMAT_MESSAGE_FROM_SYSTEM     |
			FORMAT_MESSAGE_IGNORE_INSERTS,             /* dwFlags */
			NULL,                                      /* lpSource */
			GetLastError (),                           /* dwMessageId */
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* dwLanguageId */
			(LPSTR) &lpMsgBuf,                         /* lpBuffer */
			0,                                         /* nSize */
			NULL                                       /* Arguments */
		))
		{

			fprintf (stderr, "[filesystem] CreateFileW(\"%s\"): %s\n", path, lpMsgBuf);
			LocalFree (lpMsgBuf);
		}
		free (wpath);
		free (path);
		return 0;
	}
	free (wpath);
	free (path);

	r = calloc (1, sizeof (*r));
	if (!r)
	{ /* out of memory */
		CloseHandle (fd);
		return 0;
	}
	r->fd = fd;
	r->owner = s;
	s->head.ref(&s->head);
	ocpfilehandle_t_fill
	(
		&r->head,
		windows_filehandle_ref,
		windows_filehandle_unref,
		_s,
		windows_filehandle_seek_set,
		windows_filehandle_getpos,
		windows_filehandle_eof,
		windows_filehandle_error,
		windows_filehandle_read,
		0, /* ioctl */
		windows_filehandle_filesize,
		windows_filehandle_filesize_ready,
		0, /* filename_override */
		dirdbRef (s->head.dirdb_ref, dirdb_use_filehandle),
		1
	);

	return &r->head;
}

static uint64_t windows_file_filesize (struct ocpfile_t *_s)
{
	struct windows_ocpfile_t *s = (struct windows_ocpfile_t *)_s;

	return s->filesize;
}

static int windows_file_filesize_ready (struct ocpfile_t *_s)
{
	return 1;
}

static void filesystem_windows_add_drive (const int index, const char DriveLetter)
{
	char drivename[13];
	snprintf (drivename, sizeof (drivename), "%c:", DriveLetter);

	if (!dmDriveRoots[index])
	{
		uint32_t dirdb_node = dirdbFindAndRef (DIRDB_NOPARENT, drivename, dirdb_use_dir);

		if (dirdb_node == DIRDB_CLEAR)
		{
			fprintf (stderr, "filesystem_windows_add_drive(\"%c:\"): dirdbFindAndRef() failed\n", DriveLetter);
			return;
		}

		dmDriveRoots[index] = windows_dir_steal (0, dirdb_node);
	}

	dmDriveLetters[index] = RegisterDrive (drivename, dmDriveRoots[index], dmDriveRoots[index]);
}

static void filesystem_windows_remove_drive (const int index)
{
	int i;
	if (dmDriveLetters[index])
	{
/* If the drive we are removing is the current active drive, update it */
		if (dmCurDrive == dmDriveLetters[index])
		{
			for (i=0; i < 26; i++)
			{
				int j = (i + 2) % 26;
				if (j == index)
				{
					continue;
				}
				if (dmDriveLetters[j])
				{
					dmCurDrive = dmDriveLetters[j];
					dmLastActiveDriveLetter = dmCurDrive->drivename[0];
					break;
				}
			}
			if (i == 26)
			{
				dmCurDrive = dmSetup;
			}
		} else if ((dmLastActiveDriveLetter - 'A') == index)
		{
			dmLastActiveDriveLetter = 0;
		}
		UnregisterDrive (dmDriveLetters[index]);
		dmDriveLetters[index] = 0;
	}
}

void filesystem_windows_refresh_drives (void)
{
	char i;
	DWORD DrivesMask = GetLogicalDrives ();
	for (i='A'; i <= 'Z'; i++)
	{
		if (DrivesMask & (1 << (i - 'A')))
		{
			if (!dmDriveLetters[i - 'A'])
			{
				filesystem_windows_add_drive (i - 'A', i);
			}
		} else {
			if (dmDriveLetters[i - 'A'])
			{
				filesystem_windows_remove_drive (i - 'A');
			}
		}
	}
}

// steals the dirdb reference
static struct ocpfile_t *windows_file_steal (struct ocpdir_t *parent, const uint32_t dirdb_node, uint64_t filesize)
{
	struct windows_ocpfile_t *r;

	r = calloc (1, sizeof (*r));

	if (!r)
	{ /* out of memory */
		dirdbUnref (dirdb_node, dirdb_use_file);
		return 0;
	}

	ocpfile_t_fill
	(
		&r->head,
		windows_file_ref,
		windows_file_unref,
		parent,
		windows_file_open,
		windows_file_filesize,
		windows_file_filesize_ready,
		0, /* filename_override */
		dirdb_node,
		1, /* refcount */
	        0, /* is_nodetect */
		COMPRESSION_NONE
	);

	parent->ref (parent);
	r->filesize = filesize;

	return &r->head;
}

static struct ocpdir_t *windows_dir_readdir_dir (struct ocpdir_t *_s, uint32_t dirdb_ref)
{ /* The use of FindFirstFileW() makes it ignore cases */
	struct windows_ocpdir_t *s = (struct windows_ocpdir_t *)_s;
	char *path;
	uint16_t *wpath;
	HANDLE FindHandle;
	WIN32_FIND_DATAW FindData;
	struct ocpdir_t *retval;

	dirdbGetFullname_malloc (dirdb_ref, &path, DIRDB_FULLNAME_BACKSLASH);
	if (!path)
	{
		fprintf (stderr, "[filesystem windows readdir_dir]: dirdbGetFullname_malloc () failed\n");
		return 0;
	}

	wpath = utf8_to_utf16_LFN (path, 0);
	if (!wpath)
	{
		fprintf (stderr, "[filesystem windows readdir_dir] utf8_to_utf16() failed\n");
		free (path);
		return 0;
	}
	free (path);
	path = 0;

	FindHandle = FindFirstFileW (wpath, &FindData);
	free (wpath);
	wpath = 0;
	if (FindHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	if (!(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
	{
		FindClose (FindHandle);
		return 0;
	}

	dirdbRef(dirdb_ref, dirdb_use_dir);
	retval = windows_dir_steal (&s->head, dirdb_ref);

	FindClose (FindHandle);

	return retval;
}

static struct ocpfile_t *windows_dir_readdir_file (struct ocpdir_t *_s, uint32_t dirdb_ref)
{ /* The use of FindFirstFileW() makes it ignore cases */
	struct windows_ocpdir_t *s = (struct windows_ocpdir_t *)_s;
	char *path;
	uint16_t *wpath;
	HANDLE FindHandle;
	WIN32_FIND_DATAW FindData;
	struct ocpfile_t *retval;

	dirdbGetFullname_malloc (dirdb_ref, &path, DIRDB_FULLNAME_BACKSLASH);
	if (!path)
	{
		fprintf (stderr, "[filesystem windows readdir_file]: dirdbGetFullname_malloc () failed\n");
		return 0;
	}

	wpath = utf8_to_utf16_LFN (path, 0);
	if (!wpath)
	{
		fprintf (stderr, "[filesystem windows readdir_file] utf8_to_utf16() failed\n");
		free (path);
		return 0;
	}
	free (path);
	path = 0;

	FindHandle = FindFirstFileW (wpath, &FindData);
	free (wpath);
	wpath = 0;
	if (FindHandle == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	{
		FindClose (FindHandle);
		return 0;
	}

	dirdbRef(dirdb_ref, dirdb_use_file);
	retval = windows_file_steal (&s->head, dirdb_ref, ((uint64_t)(FindData.nFileSizeHigh) << 32) | FindData.nFileSizeLow);

	FindClose (FindHandle);

	return retval;
}

// steals the dirdb reference
static struct ocpdir_t *windows_dir_steal (struct ocpdir_t *parent, const uint32_t dirdb_node)
{
	struct windows_ocpdir_t *r;
	r = calloc (1, sizeof (*r));

	if (!r)
	{ /* out of memory */
		dirdbUnref (dirdb_node, dirdb_use_dir);
		return 0;
	}

	ocpdir_t_fill (&r->head,
	               windows_dir_ref,
	               windows_dir_unref,
	               parent,
	               windows_dir_readdir_start,
	               0, /* windows_readflatdir_start */
	               windows_dir_readdir_cancel,
	               windows_dir_readdir_iterate,
	               windows_dir_readdir_dir,
	               windows_dir_readdir_file,
	               0, /* charset_override_API */
	               dirdb_node,
	               1, /* refcount */
	               0, /* is_archive */
	               0, /* is_playlist */
	               COMPRESSION_NONE);

	if (parent)
	{
		parent->ref (parent);
	}
	return &r->head;
}

static struct ocpdir_t *filesystem_windows_resolve_dir (const char *path)
{
	uint32_t dirdb_ref;
	struct dmDrive *drive = 0;
	struct ocpdir_t *dir = 0;
	uint16_t *wpath;
	uint16_t *wlongpath;
	DWORD len;
	char *longpath;

	if (!(wpath = utf8_to_utf16_LFN (path, 0)))
	{
		fprintf (stderr, "filesystem_windows_resolve_dir(): malloc failed #1\n");
		return 0;
	}

	len = GetLongPathNameW (wpath, 0, 0);
	if (!(wlongpath = malloc (len * sizeof (uint16_t))))
	{
		fprintf (stderr, "filesystem_windows_resolve_dir(): malloc failed #2\n");
		free (wpath);
		return 0;
	}

	if (GetLongPathNameW (wpath, wlongpath, len) != (len - 1))
	{
		char *lpMsgBuf = NULL;
		if (FormatMessage (
			FORMAT_MESSAGE_ALLOCATE_BUFFER |
			FORMAT_MESSAGE_FROM_SYSTEM     |
			FORMAT_MESSAGE_IGNORE_INSERTS,             /* dwFlags */
			NULL,                                      /* lpSource */
			GetLastError (),                           /* dwMessageId */
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* dwLanguageId */
			(LPSTR) &lpMsgBuf,                         /* lpBuffer */
			0,                                         /* nSize */
			NULL                                       /* Arguments */
		))
		{

			fprintf (stderr, "[filesystem] GetLongPathNameW(L\"%s\"): %s\n", path, lpMsgBuf);
			LocalFree (lpMsgBuf);
		}

		free (wpath);
		free (wlongpath);
		return 0;
	}

	longpath = utf16_to_utf8 (wlongpath);

	free (wpath);
	wpath = 0;
	free (wlongpath);
	wlongpath = 0;

	if (strncmp (longpath, "\\\\?\\", 4))
	{
		dirdb_ref = dirdbResolvePathWithBaseAndRef (DIRDB_CLEAR, longpath, DIRDB_RESOLVE_DRIVE | DIRDB_RESOLVE_WINDOWS_SLASH, dirdb_use_dir);
	} else {
		dirdb_ref = dirdbResolvePathWithBaseAndRef (DIRDB_CLEAR, longpath + 4, DIRDB_RESOLVE_DRIVE | DIRDB_RESOLVE_WINDOWS_SLASH, dirdb_use_dir);
	}

	free (longpath);
	longpath = 0;

	if (!filesystem_resolve_dirdb_dir (dirdb_ref, &drive, &dir))
	{
		if (drive->drivename[1] != ':')
		{
			dir->unref (dir);
			dir = 0;
		}
	}
	dirdbUnref (dirdb_ref, dirdb_use_dir);
	return dir;
}

struct dmDrive *filesystem_windows_init (void)
{
	char *path;
	uint16_t *wpath;
	DWORD length;
	uint32_t newcurrentpath;
	int driveindex;
	char i;

	struct ocpdir_t *newcwd;
	struct dmDrive *newdrive;

	filesystem_windows_refresh_drives();

	if (!(length = GetCurrentDirectoryW (0, NULL)))
	{
		goto failed;
	}

	if (!(wpath = calloc (length, sizeof (uint16_t))))
	{
		goto failed;
	}

	if (GetCurrentDirectoryW (length, wpath) != (length - 1))
	{
		free (wpath);
		goto failed;
	}

	path = utf16_to_utf8 (wpath);
	free (wpath);
	wpath = 0;
	if (!path)
	{
		goto failed;
	}

	if ((path[0] == '\\' || path[1] == '\\')) // network share
	{
		free (path);
		goto failed;
	}
	if (path[1] != ':') // not a drive????
	{
		free (path);
		goto failed;
	}
	driveindex = toupper (path[0]) - 'A';
	if (!dmDriveLetters[driveindex])
	{
		free (path);
		goto failed;
	}

	if (!(configAPI.HomeDir       = filesystem_windows_resolve_dir    (configAPI.HomePath       ))) { fprintf (stderr, "Unable to resolve cfHome=\"%s\"\n",       configAPI.HomePath);       goto failed; }
	if (!(configAPI.ConfigHomeDir = filesystem_windows_resolve_dir    (configAPI.ConfigHomePath ))) { fprintf (stderr, "Unable to resolve cfConfigHome=\"%s\"\n", configAPI.ConfigHomePath); goto failed; }
	if (!(configAPI.DataHomeDir   = filesystem_windows_resolve_dir    (configAPI.DataHomePath   ))) { fprintf (stderr, "Unable to resolve cfDataHome=\"%s\"\n",   configAPI.DataHomePath);   goto failed; }
	if (!(configAPI.DataDir       = filesystem_windows_resolve_dir    (configAPI.DataPath       ))) { fprintf (stderr, "Unable to resolve cfData=\"%s\"\n",       configAPI.DataPath);       goto failed; }
	if (!(configAPI.TempDir       = filesystem_windows_resolve_dir    (configAPI.TempPath       ))) { fprintf (stderr, "Unable to resolve cfTemp=\"%s\"\n",       configAPI.TempPath);       goto failed; }

	newcurrentpath = dirdbResolvePathWithBaseAndRef (dmDriveRoots[driveindex]->dirdb_ref, path, DIRDB_RESOLVE_WINDOWS_SLASH | DIRDB_RESOLVE_DRIVE, dirdb_use_dir);
	free (path);
	path = 0;
	if (newcurrentpath == DIRDB_CLEAR)
	{
		goto failed;
	}

	if (!filesystem_resolve_dirdb_dir (newcurrentpath, &newdrive, &newcwd))
	{
		newdrive->cwd->unref (newdrive->cwd);
		newdrive->cwd = newcwd;
	}
	dirdbUnref (newcurrentpath, dirdb_use_dir);

	if (newdrive)
	{
		dmLastActiveDriveLetter = newdrive->drivename[0];
	}

	return newdrive;

failed:
	for (i='C'; i <= 'Z'; i++)
	{
		if (dmDriveLetters[i-'A'])
		{
			return dmDriveLetters[i-'A'];
		}
	}
	for (i='A'; i < 'C'; i++)
	{
		if (dmDriveLetters[i-'A'])
		{
			return dmDriveLetters[i-'A'];
		}
	}

	return 0;
}

void filesystem_windows_done (void)
{
	int i;
	for (i='A'; i <= 'Z'; i++)
	{
		if (dmDriveLetters[i - 'A'])
		{
			filesystem_windows_remove_drive (i - 'A');
			dmDriveLetters[i - 'A'] = 0;
		}
	}
	dmLastActiveDriveLetter = 0;
}
