From 5f0c5d10998ab8d22737d84dc24b9906c4bf43bf Mon Sep 17 00:00:00 2001
From: Ghabry <gabriel+github@mastergk.de>
Date: Mon, 1 Apr 2024 15:06:20 +0200
Subject: [PATCH 1/5] Game Browser: Support going up from the startup directory

---
 src/filefinder.cpp        |  2 +-
 src/filesystem.cpp        | 14 ++++++++++++++
 src/filesystem.h          | 13 +++++++++++++
 src/main_data.cpp         | 28 ++++++++++++++++++----------
 src/scene_gamebrowser.cpp | 18 ++++++++++++------
 src/window_gamelist.cpp   |  8 +++++++-
 tests/filefinder.cpp      |  4 ----
 tests/output.cpp          |  1 -
 8 files changed, 65 insertions(+), 23 deletions(-)

diff --git a/src/filefinder.cpp b/src/filefinder.cpp
index cc6b9004cc..bc08b96770 100644
--- a/src/filefinder.cpp
+++ b/src/filefinder.cpp
@@ -143,7 +143,7 @@ FilesystemView FileFinder::Root() {
 		root_fs = std::make_unique<RootFilesystem>();
 	}
 
-	return root_fs->Subtree("");
+	return *root_fs;
 }
 
 std::string FileFinder::MakePath(StringView dir, StringView name) {
diff --git a/src/filesystem.cpp b/src/filesystem.cpp
index 132558f60c..a6757392a6 100644
--- a/src/filesystem.cpp
+++ b/src/filesystem.cpp
@@ -382,6 +382,20 @@ FilesystemView FilesystemView::Subtree(StringView sub_path) const {
 	return FilesystemView(fs, MakePath(sub_path));
 }
 
+bool FilesystemView::CanGoUp() const {
+	return static_cast<bool>(GoUp());
+}
+
+FilesystemView FilesystemView::GoUp() const {
+	if (GetSubPath().empty() || GetSubPath() == "/") {
+		return fs->GetParent();
+	}
+
+	auto [path, file] = FileFinder::GetPathAndFilename(GetSubPath());
+
+	return FilesystemView(fs, path);
+}
+
 std::string FilesystemView::Describe() const {
 	assert(fs);
 	if (GetSubPath().empty()) {
diff --git a/src/filesystem.h b/src/filesystem.h
index 02936dcdee..736d476016 100644
--- a/src/filesystem.h
+++ b/src/filesystem.h
@@ -475,6 +475,19 @@ class FilesystemView {
 	 */
 	FilesystemView Subtree(StringView sub_path) const;
 
+	/**
+	 * @return Whether it is possible to go up from the current view
+	 */
+	bool CanGoUp() const;
+
+	/**
+	 * From the current view goes up by one.
+	 * Returns an invalid view when going up is not possible (no parent).
+	 *
+	 * @return View that is rooted at the parent.
+	 */
+	FilesystemView GoUp() const;
+
 	/** @return human readable representation of this filesystem for debug purposes */
 	std::string Describe() const;
 
diff --git a/src/main_data.cpp b/src/main_data.cpp
index 86d4a8cc31..33a6a8ff01 100644
--- a/src/main_data.cpp
+++ b/src/main_data.cpp
@@ -41,9 +41,12 @@
 #include "system.h"
 #include "output.h"
 
-#ifndef _WIN32
+#ifdef _WIN32
+#  include <windows.h>
+#else
 #  include <unistd.h>
 #endif
+
 #if defined(USE_SDL) && defined(__ANDROID__)
 #  include <jni.h>
 #  include <SDL_system.h>
@@ -89,22 +92,27 @@ void Main_Data::Init() {
 			// Set to current directory
 			project_path = "";
 
-#if defined(PLAYER_AMIGA)
-			// Working directory not correctly handled
-			char working_dir[256];
-			getcwd(working_dir, 255);
-			project_path = std::string(working_dir);
-#elif defined(__APPLE__) && TARGET_OS_OSX
+#ifdef _WIN32
+			wchar_t working_dir[MAX_PATH];
+			if (GetCurrentDirectory(MAX_PATH, working_dir) != 0) {
+				project_path = Utils::FromWideString(working_dir);
+			}
+#else
+			char working_dir[PATH_MAX];
+			if (getcwd(working_dir, sizeof(working_dir))) {
+				project_path = std::string(working_dir);
+			}
+
+#  if defined(__APPLE__) && TARGET_OS_OSX
 			// Apple Finder does not set the working directory
 			// It points to HOME instead. When it is HOME change it to
 			// the application directory instead
 
 			char* home = getenv("HOME");
-			char current_dir[PATH_MAX] = { 0 };
-			getcwd(current_dir, sizeof(current_dir));
-			if (strcmp(current_dir, "/") == 0 || strcmp(current_dir, home) == 0) {
+			if (strcmp(working_dir, "/") == 0 || strcmp(working_dir, home) == 0) {
 				project_path = MacOSUtils::GetBundleDir();
 			}
+#  endif
 #endif
 		}
 	}
diff --git a/src/scene_gamebrowser.cpp b/src/scene_gamebrowser.cpp
index 5a0b700569..06b731b8f5 100644
--- a/src/scene_gamebrowser.cpp
+++ b/src/scene_gamebrowser.cpp
@@ -99,9 +99,9 @@ void Scene_GameBrowser::CreateWindows() {
 	command_window->SetIndex(0);
 
 	gamelist_window = std::make_unique<Window_GameList>(0, 64, Player::screen_width, Player::screen_height - 64);
-	gamelist_window->Refresh(stack.back().filesystem, false);
+	gamelist_window->Refresh(stack.back().filesystem, stack.back().filesystem.CanGoUp());
 
-	if (stack.size() == 1 && !gamelist_window->HasValidEntry()) {
+	if (stack.size() == 1 && !stack.back().filesystem.CanGoUp() && !gamelist_window->HasValidEntry()) {
 		command_window->DisableItem(0);
 	}
 
@@ -140,7 +140,7 @@ void Scene_GameBrowser::UpdateCommand() {
 
 		switch (menu_index) {
 			case GameList:
-				if (stack.size() == 1 && !gamelist_window->HasValidEntry()) {
+				if (!command_window->IsItemEnabled(0)) {
 					return;
 				}
 				command_window->SetActive(false);
@@ -177,11 +177,17 @@ void Scene_GameBrowser::UpdateGameListSelection() {
 }
 
 void Scene_GameBrowser::BootGame() {
-	if (stack.size() > 1 && gamelist_window->GetIndex() == 0) {
+	if (stack.back().filesystem.CanGoUp() && gamelist_window->GetIndex() == 0) {
 		// ".." -> Go one level up
 		int index = stack.back().index;
-		stack.pop_back();
-		gamelist_window->Refresh(stack.back().filesystem, stack.size() > 1);
+
+		if (stack.size() == 1) {
+			stack.back() = {stack.back().filesystem.GoUp(), 0};
+		} else {
+			stack.pop_back();
+		}
+
+		gamelist_window->Refresh(stack.back().filesystem, stack.back().filesystem.CanGoUp());
 		gamelist_window->SetIndex(index);
 		load_window->SetVisible(false);
 		game_loading = false;
diff --git a/src/window_gamelist.cpp b/src/window_gamelist.cpp
index bf6b58fed0..7a0d2c7ce0 100644
--- a/src/window_gamelist.cpp
+++ b/src/window_gamelist.cpp
@@ -111,6 +111,12 @@ void Window_GameList::DrawItem(int index) {
 	contents->TextDraw(rect.x, rect.y, Font::ColorDefault, game_directories[index]);
 }
 
+#ifdef HAVE_LHASA
+#define LZH_STR "/LZH"
+#else
+#define LZH_STR ""
+#endif
+
 void Window_GameList::DrawErrorText(bool show_dotdot) {
 	std::vector<std::string> error_msg = {
 #ifdef EMSCRIPTEN
@@ -128,7 +134,7 @@ void Window_GameList::DrawErrorText(bool show_dotdot) {
 		"with RPG Maker 2000 and RPG Maker 2003.",
 		"",
 		"These games have an RPG_RT.ldb and they can be",
-		"extracted or in ZIP archives.",
+		"extracted or in ZIP" LZH_STR " archives.",
 		"",
 		"Newer engines such as RPG Maker XP, VX, MV and MZ",
 		"are not supported."
diff --git a/tests/filefinder.cpp b/tests/filefinder.cpp
index 60cabf16d8..b40f931c04 100644
--- a/tests/filefinder.cpp
+++ b/tests/filefinder.cpp
@@ -8,8 +8,6 @@
 TEST_SUITE_BEGIN("FileFinder");
 
 TEST_CASE("IsRPG2kProject") {
-	Main_Data::Init();
-
 	Player::escape_symbol = "\\";
 
 	auto fs = FileFinder::Root().Subtree(EP_TEST_PATH "/game");
@@ -19,8 +17,6 @@ TEST_CASE("IsRPG2kProject") {
 }
 
 TEST_CASE("IsNotRPG2kProject") {
-	Main_Data::Init();
-
 	auto fs = FileFinder::Root().Subtree(EP_TEST_PATH "/notagame");
 	CHECK(!FileFinder::IsRPG2kProject(fs));
 }
diff --git a/tests/output.cpp b/tests/output.cpp
index 0305c06768..33bf759247 100644
--- a/tests/output.cpp
+++ b/tests/output.cpp
@@ -7,7 +7,6 @@ TEST_SUITE_BEGIN("Output");
 
 TEST_CASE("Message Output") {
 	Graphics::Init();
-	Main_Data::Init();
 	Output::Debug("Test {}", "debg");
 	Output::Warning("Test {}", "test");
 	Output::Info("Test {}", "info");

From 3bba5b9effd1c57163a2c1afaac01b23ba464446 Mon Sep 17 00:00:00 2001
From: Ghabry <gabriel+github@mastergk.de>
Date: Mon, 1 Apr 2024 17:50:25 +0200
Subject: [PATCH 2/5] Add Drive Filesystem

Is a virtual filesystem that lists drive letters on e.g. Windows.
The filesystem is deactivated on all other platforms right now.

Invented a new filetype "Filesystem" for delegation of the drive FS to the native FS.
This is kinda a hack but I couldn't think of a better way to add this...
---
 CMakeLists.txt            |   2 +
 Makefile.am               |   2 +
 src/directory_tree.h      |   9 ++++
 src/filesystem.cpp        |  32 +++++++++--
 src/filesystem.h          |  16 ++++++
 src/filesystem_drive.cpp  | 110 ++++++++++++++++++++++++++++++++++++++
 src/filesystem_drive.h    |  56 +++++++++++++++++++
 src/filesystem_root.cpp   |  15 ++++--
 src/scene_gamebrowser.cpp |   4 +-
 src/window_gamelist.cpp   |  22 ++++----
 src/window_gamelist.h     |   6 +--
 11 files changed, 251 insertions(+), 23 deletions(-)
 create mode 100644 src/filesystem_drive.cpp
 create mode 100644 src/filesystem_drive.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 88c93137a9..86b309cc2a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -120,6 +120,8 @@ add_library(${PROJECT_NAME} OBJECT
 	src/fileext_guesser.h
 	src/filesystem.cpp
 	src/filesystem.h
+	src/filesystem_drive.cpp
+	src/filesystem_drive.h	
 	src/filesystem_lzh.cpp
 	src/filesystem_lzh.h
 	src/filesystem_native.cpp
diff --git a/Makefile.am b/Makefile.am
index c05af3518a..0390b833b2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -103,6 +103,8 @@ libeasyrpg_player_a_SOURCES = \
 	src/fileext_guesser.h \
 	src/filesystem.cpp \
 	src/filesystem.h \
+	src/filesystem_drive.cpp \
+	src/filesystem_drive.h \
 	src/filesystem_lzh.cpp \
 	src/filesystem_lzh.h \
 	src/filesystem_native.cpp \
diff --git a/src/directory_tree.h b/src/directory_tree.h
index 6232f5814e..71f72ba50f 100644
--- a/src/directory_tree.h
+++ b/src/directory_tree.h
@@ -43,6 +43,8 @@ class DirectoryTree {
 		Regular,
 		/** Directory */
 		Directory,
+		/** A virtual directory that will access a different virtual filesystem */
+		Filesystem,
 		/** Anything of no interest such as block devices */
 		Other
 	};
@@ -53,8 +55,15 @@ class DirectoryTree {
 		std::string name;
 		/** File type */
 		FileType type;
+		/** Human readable name shown in the Game Browser (if different to the filename) */
+		std::string human_name;
 
 		Entry(std::string name, FileType type) : name(std::move(name)), type(type) {}
+		Entry(std::string name, FileType type, std::string human_name) : name(std::move(name)), type(type), human_name(std::move(human_name)) {}
+
+		StringView GetReadableName() const {
+			return human_name.empty() ? name : human_name;
+		}
 	};
 
 	/** Argument struct for more complex find operations */
diff --git a/src/filesystem.cpp b/src/filesystem.cpp
index a6757392a6..1649512b5f 100644
--- a/src/filesystem.cpp
+++ b/src/filesystem.cpp
@@ -87,9 +87,13 @@ void Filesystem::ClearCache(StringView path) const {
 FilesystemView Filesystem::Create(StringView path) const {
 	// Determine the proper file system to use
 
-	// When the path doesn't exist check if the path contains a file that can
-	// be handled by another filesystem
-	if (!IsDirectory(path, true)) {
+	if (IsFilesystemNode(path)) {
+		// The support for "mounted" virtual filesystems is very limited and the only
+		// use right now is to delegate from DriveFilesystem to NativeFilesystem.
+		return CreateFromNode(path);
+	} else if (!IsDirectory(path, true)) {
+		// When the path doesn't exist check if the path contains a file that can
+		// be handled by another filesystem
 		std::string dir_of_file;
 		std::string path_prefix;
 		std::vector<std::string> components = FileFinder::SplitPath(path);
@@ -175,10 +179,18 @@ FilesystemView Filesystem::Subtree(std::string sub_path) const {
 	return FilesystemView(shared_from_this(), sub_path);
 }
 
+bool Filesystem::IsFilesystemNode(StringView) const {
+	return false;
+}
+
 bool Filesystem::MakeDirectory(StringView, bool) const {
 	return false;
 }
 
+FilesystemView Filesystem::CreateFromNode(StringView) const {
+	return FilesystemView();
+}
+
 bool Filesystem::IsValid() const {
 	// FIXME: better way to do this?
 	return Exists("");
@@ -307,6 +319,11 @@ bool FilesystemView::IsDirectory(StringView path, bool follow_symlinks) const {
 	return fs->IsDirectory(MakePath(path), follow_symlinks);
 }
 
+bool FilesystemView::IsFilesystemNode(StringView path) const {
+	assert(fs);
+	return fs->IsFilesystemNode(MakePath(path));
+}
+
 bool FilesystemView::Exists(StringView path) const {
 	assert(fs);
 	return fs->Exists(MakePath(path));
@@ -372,6 +389,11 @@ bool FilesystemView::MakeDirectory(StringView dir, bool follow_symlinks) const {
 	return fs->MakeDirectory(MakePath(dir), follow_symlinks);
 }
 
+FilesystemView FilesystemView::CreateFromNode(StringView path) const {
+	assert(fs);
+	return fs->CreateFromNode(MakePath(path));
+}
+
 bool FilesystemView::IsFeatureSupported(Filesystem::Feature f) const {
 	assert(fs);
 	return fs->IsFeatureSupported(f);
@@ -393,6 +415,10 @@ FilesystemView FilesystemView::GoUp() const {
 
 	auto [path, file] = FileFinder::GetPathAndFilename(GetSubPath());
 
+	if (path == GetSubPath()) {
+		return fs->GetParent();
+	}
+
 	return FilesystemView(fs, path);
 }
 
diff --git a/src/filesystem.h b/src/filesystem.h
index 736d476016..096deca019 100644
--- a/src/filesystem.h
+++ b/src/filesystem.h
@@ -217,9 +217,11 @@ class Filesystem : public std::enable_shared_from_this<Filesystem> {
 	/** @{ */
 	virtual bool IsFile(StringView path) const = 0;
 	virtual bool IsDirectory(StringView path, bool follow_symlinks) const = 0;
+	virtual bool IsFilesystemNode(StringView path) const;
 	virtual bool Exists(StringView path) const = 0;
 	virtual int64_t GetFilesize(StringView path) const = 0;
 	virtual bool MakeDirectory(StringView dir, bool follow_symlinks) const;
+	virtual FilesystemView CreateFromNode(StringView path) const;
 	virtual bool IsFeatureSupported(Feature f) const;
 	virtual std::string Describe() const = 0;
 	/** @} */
@@ -369,6 +371,12 @@ class FilesystemView {
 	 */
 	bool IsDirectory(StringView path, bool follow_symlinks) const;
 
+	/**
+	 * @param path Path to check
+	 * @return True when path is pointing to a virtual filesystem
+	 */
+	bool IsFilesystemNode(StringView path) const;
+
 	/**
 	 * @param path Path to check
 	 * @return True when a file exists at the path
@@ -461,6 +469,14 @@ class FilesystemView {
 	 */
 	bool MakeDirectory(StringView dir, bool follow_symlinks) const;
 
+	/**
+	 * Create a filesystem view from the passed in path.
+	 * The path must point to a virtual filesystem entry (type Filesystem).
+	 * @param path Path to create filesystem from
+	 * @return view pointing at the new fs
+	 */
+	FilesystemView CreateFromNode(StringView path) const;
+
 	/**
 	 * @param f Filesystem feature to check
 	 * @return true when the feature is supported.
diff --git a/src/filesystem_drive.cpp b/src/filesystem_drive.cpp
new file mode 100644
index 0000000000..67aced70f4
--- /dev/null
+++ b/src/filesystem_drive.cpp
@@ -0,0 +1,110 @@
+/*
+ * This file is part of EasyRPG Player.
+ *
+ * EasyRPG Player 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.
+ *
+ * EasyRPG Player 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 EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "filesystem_drive.h"
+#include "filefinder.h"
+#include "output.h"
+
+#include <algorithm>
+
+#ifdef _WIN32
+#  include <windows.h>
+#  include <fileapi.h>
+#endif
+
+DriveFilesystem::DriveFilesystem() : Filesystem("", FilesystemView()) {
+#ifdef _WIN32
+	std::wstring volume = L"A:\\";
+
+	DWORD logical_drives = GetLogicalDrives();
+	for (int i = 0; i < 26; i++) {
+		if ((logical_drives & (1 << i)) > 0) {
+			DirectoryTree::Entry entry = { Utils::FromWideString(volume), DirectoryTree::FileType::Filesystem };
+
+			wchar_t volume_name[MAX_PATH];
+			if (GetVolumeInformation(volume.c_str(), volume_name, MAX_PATH, nullptr, nullptr, nullptr, nullptr, 0) != 0) {
+				entry.human_name = fmt::format("{} ({})", Utils::FromWideString(volume), Utils::FromWideString(volume_name));
+			}
+
+			drives.push_back(entry);
+		}
+		volume[0]++; // Increment drive letter
+	}
+#endif
+}
+
+bool DriveFilesystem::HasDrives() const {
+	return !drives.empty();
+}
+
+bool DriveFilesystem::IsFile(StringView path) const {
+	(void)path;
+	return false;
+}
+
+bool DriveFilesystem::IsDirectory(StringView path, bool) const {
+	return path.empty();
+}
+
+bool DriveFilesystem::IsFilesystemNode(StringView path) const {
+	for (const auto& drive: drives) {
+		if (drive.name == path) {
+			return true;
+		}
+#ifdef _WIN32
+		if (drive.name == Utils::ReplaceAll(ToString(path), "/", "\\")) {
+			return true;
+		}
+#endif
+	}
+
+	return false;
+}
+
+bool DriveFilesystem::Exists(StringView path) const {
+	return IsDirectory(path, false) || IsFilesystemNode(path);
+}
+
+int64_t DriveFilesystem::GetFilesize(StringView path) const {
+	(void)path;
+	return 0;
+}
+
+FilesystemView DriveFilesystem::CreateFromNode(StringView path) const {
+	if (!IsFilesystemNode(path)) {
+		return {};
+	}
+
+	return FileFinder::Root().Create(path);
+}
+
+std::streambuf* DriveFilesystem::CreateInputStreambuffer(StringView path, std::ios_base::openmode mode) const {
+	return nullptr;
+}
+
+bool DriveFilesystem::GetDirectoryContent(StringView path, std::vector<DirectoryTree::Entry>& tree) const {
+	if (!path.empty()) {
+		return false;
+	}
+
+	tree = drives;
+	return true;
+}
+
+std::string DriveFilesystem::Describe() const {
+	return "[Drive]";
+}
diff --git a/src/filesystem_drive.h b/src/filesystem_drive.h
new file mode 100644
index 0000000000..83985f9ee9
--- /dev/null
+++ b/src/filesystem_drive.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of EasyRPG Player.
+ *
+ * EasyRPG Player 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.
+ *
+ * EasyRPG Player 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 EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EP_FILESYSTEM_DRIVE_H
+#define EP_FILESYSTEM_DRIVE_H
+
+#include "filesystem.h"
+
+/**
+ * A virtual filesystem that lists e.g. drive letters on Windows
+ */
+class DriveFilesystem : public Filesystem {
+public:
+	/**
+	 * Initializes a OS Filesystem on the given os path
+	 */
+	explicit DriveFilesystem();
+
+	/** @return Whether the current target platform has drive letters to list */
+	bool HasDrives() const;
+
+protected:
+	/**
+ 	 * Implementation of abstract methods
+ 	 */
+	/** @{ */
+	bool IsFile(StringView path) const override;
+	bool IsDirectory(StringView path, bool follow_symlinks) const override;
+	bool IsFilesystemNode(StringView path) const override;
+	bool Exists(StringView path) const override;
+	int64_t GetFilesize(StringView path) const override;
+	FilesystemView CreateFromNode(StringView path) const override;
+	std::streambuf* CreateInputStreambuffer(StringView path, std::ios_base::openmode mode) const override;
+	bool GetDirectoryContent(StringView path, std::vector<DirectoryTree::Entry>& entries) const override;
+	std::string Describe() const override;
+	/** @} */
+
+private:
+	std::vector<DirectoryTree::Entry> drives;
+};
+
+#endif
diff --git a/src/filesystem_root.cpp b/src/filesystem_root.cpp
index 270c6efa7f..4270cfcd47 100644
--- a/src/filesystem_root.cpp
+++ b/src/filesystem_root.cpp
@@ -16,6 +16,7 @@
  */
 
 #include "filesystem_root.h"
+#include "filesystem_drive.h"
 #include "output.h"
 
 #if defined(__ANDROID__) && !defined(USE_LIBRETRO)
@@ -28,12 +29,20 @@ constexpr const StringView root_ns = "root://";
 RootFilesystem::RootFilesystem() : Filesystem("", FilesystemView()) {
 	// Add platform specific namespaces here
 #if defined(__ANDROID__) && !defined(USE_LIBRETRO)
-	fs_list.push_back(std::make_pair("apk", std::make_unique<ApkFilesystem>()));
-	fs_list.push_back(std::make_pair("content", std::make_unique<SafFilesystem>("", FilesystemView())));
+	fs_list.push_back(std::make_pair("apk", std::make_shared<ApkFilesystem>()));
+	fs_list.push_back(std::make_pair("content", std::make_shared<SafFilesystem>("", FilesystemView())));
 #endif
 
+	// Support for drive letters on e.g. Windows (and similiar concepts on other platforms)
+	auto drive_fs = std::make_shared<DriveFilesystem>();
+	FilesystemView drive_view;
+	if (drive_fs->HasDrives()) {
+		drive_view = *drive_fs;
+		fs_list.push_back(std::make_pair("drive", drive_fs));
+	}
+
 	// IMPORTANT: This must be the last filesystem in the list, do not push anything to fs_list afterwards!
-	fs_list.push_back(std::make_pair("file", std::make_unique<NativeFilesystem>("", FilesystemView())));
+	fs_list.push_back(std::make_pair("file", std::make_shared<NativeFilesystem>("", drive_view)));
 
 	assert(fs_list.back().first == "file" && "File namespace must be last!");
 }
diff --git a/src/scene_gamebrowser.cpp b/src/scene_gamebrowser.cpp
index 06b731b8f5..38562e49cd 100644
--- a/src/scene_gamebrowser.cpp
+++ b/src/scene_gamebrowser.cpp
@@ -194,9 +194,7 @@ void Scene_GameBrowser::BootGame() {
 		return;
 	}
 
-	FilesystemView fs;
-	std::string entry;
-	std::tie(fs, entry) = gamelist_window->GetGameFilesystem();
+	FilesystemView fs = gamelist_window->GetGameFilesystem();
 
 	if (!fs) {
 		Output::Warning("The selected file or directory cannot be opened");
diff --git a/src/window_gamelist.cpp b/src/window_gamelist.cpp
index 7a0d2c7ce0..35c89d7af4 100644
--- a/src/window_gamelist.cpp
+++ b/src/window_gamelist.cpp
@@ -55,21 +55,21 @@ bool Window_GameList::Refresh(FilesystemView filesystem_base, bool show_dotdot)
 		}
 		if (dir.second.type == DirectoryTree::FileType::Regular) {
 			if (FileFinder::IsSupportedArchiveExtension(dir.second.name)) {
-				game_directories.emplace_back(dir.second.name);
+				game_directories.emplace_back(dir.second);
 			}
-		} else if (dir.second.type == DirectoryTree::FileType::Directory) {
-			game_directories.emplace_back(dir.second.name);
+		} else if (dir.second.type == DirectoryTree::FileType::Directory || dir.second.type == DirectoryTree::FileType::Filesystem) {
+			game_directories.emplace_back(dir.second);
 		}
 	}
 
 	// Sort game list in place
 	std::sort(game_directories.begin(), game_directories.end(),
-			  [](const std::string& s, const std::string& s2) {
-				  return strcmp(Utils::LowerCase(s).c_str(), Utils::LowerCase(s2).c_str()) <= 0;
+			  [](DirectoryTree::Entry& s, DirectoryTree::Entry& s2) {
+				  return strcmp(Utils::LowerCase(s.GetReadableName()).c_str(), Utils::LowerCase(s2.GetReadableName()).c_str()) <= 0;
 			  });
 
 	if (show_dotdot) {
-		game_directories.insert(game_directories.begin(), "..");
+		game_directories.insert(game_directories.begin(), { "..", DirectoryTree::FileType::Directory });
 	}
 
 	if (HasValidEntry()) {
@@ -102,13 +102,13 @@ void Window_GameList::DrawItem(int index) {
 	Rect rect = GetItemRect(index);
 	contents->ClearRect(rect);
 
-	std::string text;
+	StringView text;
 
 	if (HasValidEntry()) {
-		text = game_directories[index];
+		text = game_directories[index].GetReadableName();
 	}
 
-	contents->TextDraw(rect.x, rect.y, Font::ColorDefault, game_directories[index]);
+	contents->TextDraw(rect.x, rect.y, Font::ColorDefault, text);
 }
 
 #ifdef HAVE_LHASA
@@ -160,6 +160,6 @@ bool Window_GameList::HasValidEntry() {
 	return game_directories.size() > minval;
 }
 
-std::pair<FilesystemView, std::string> Window_GameList::GetGameFilesystem() const {
-	return { base_fs.Create(game_directories[GetIndex()]), game_directories[GetIndex()] };
+FilesystemView Window_GameList::GetGameFilesystem() const {
+	return base_fs.Create(game_directories[GetIndex()].name);
 }
diff --git a/src/window_gamelist.h b/src/window_gamelist.h
index a3809c6f86..4e27c47595 100644
--- a/src/window_gamelist.h
+++ b/src/window_gamelist.h
@@ -55,13 +55,13 @@ class Window_GameList : public Window_Selectable {
 	bool HasValidEntry();
 
 	/**
-	 * @return filesystem and entry name of the selected game
+	 * @return filesystem of the selected game
 	 */
-	std::pair<FilesystemView, std::string> GetGameFilesystem() const;
+	FilesystemView GetGameFilesystem() const;
 
 private:
 	FilesystemView base_fs;
-	std::vector<std::string> game_directories;
+	std::vector<DirectoryTree::Entry> game_directories;
 
 	bool show_dotdot = false;
 };

From ec57bb4ceeaaef205871bf61b1700fedc7c23d6c Mon Sep 17 00:00:00 2001
From: Ghabry <gabriel+github@mastergk.de>
Date: Mon, 1 Apr 2024 18:14:00 +0200
Subject: [PATCH 3/5] Windows: Check for correct error value for directories

---
 src/platform.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/platform.h b/src/platform.h
index a285822c7f..dacba8047c 100644
--- a/src/platform.h
+++ b/src/platform.h
@@ -157,6 +157,8 @@ namespace Platform {
 	inline Directory::operator bool() const noexcept {
 #ifdef __vita__
 		return dir_handle >= 0;
+#elif defined(_WIN32)
+		return dir_handle != INVALID_HANDLE_VALUE;
 #else
 		return dir_handle != nullptr;
 #endif

From ebdd6c87a82fea06dd09b0a4a48590a37e717df3 Mon Sep 17 00:00:00 2001
From: Ghabry <gabriel+github@mastergk.de>
Date: Mon, 1 Apr 2024 20:51:51 +0200
Subject: [PATCH 4/5] Fix Vita build (PATH_MAX not defined)

---
 src/main_data.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/main_data.cpp b/src/main_data.cpp
index 33a6a8ff01..8dd1ef6bb9 100644
--- a/src/main_data.cpp
+++ b/src/main_data.cpp
@@ -98,6 +98,10 @@ void Main_Data::Init() {
 				project_path = Utils::FromWideString(working_dir);
 			}
 #else
+
+#  ifndef PATH_MAX
+#    define PATH_MAX 256
+#  endif
 			char working_dir[PATH_MAX];
 			if (getcwd(working_dir, sizeof(working_dir))) {
 				project_path = std::string(working_dir);

From 1a24b49f184017e252ed80be64126fea62c3b525 Mon Sep 17 00:00:00 2001
From: Ghabry <gabriel+github@mastergk.de>
Date: Mon, 1 Apr 2024 20:52:30 +0200
Subject: [PATCH 5/5] Emscripten: Do not show ".." on error screen. Other
 platforms: Fix rendering of ".." on error screen.

---
 src/scene_gamebrowser.cpp |  4 ++++
 src/window_gamelist.cpp   | 11 +++++------
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/scene_gamebrowser.cpp b/src/scene_gamebrowser.cpp
index 38562e49cd..9c0f889b75 100644
--- a/src/scene_gamebrowser.cpp
+++ b/src/scene_gamebrowser.cpp
@@ -105,6 +105,10 @@ void Scene_GameBrowser::CreateWindows() {
 		command_window->DisableItem(0);
 	}
 
+#ifdef EMSCRIPTEN
+	command_window->DisableItem(0);
+#endif
+
 	help_window = std::make_unique<Window_Help>(0, 0, Player::screen_width, 32);
 	help_window->SetText("EasyRPG Player - RPG Maker 2000/2003 interpreter");
 
diff --git a/src/window_gamelist.cpp b/src/window_gamelist.cpp
index 35c89d7af4..3daee9ef13 100644
--- a/src/window_gamelist.cpp
+++ b/src/window_gamelist.cpp
@@ -84,6 +84,10 @@ bool Window_GameList::Refresh(FilesystemView filesystem_base, bool show_dotdot)
 		}
 	}
 	else {
+#ifdef EMSCRIPTEN
+	show_dotdot = false;
+#endif
+
 		item_max = 1;
 
 		SetContents(Bitmap::Create(width - 16, height - 16));
@@ -102,12 +106,7 @@ void Window_GameList::DrawItem(int index) {
 	Rect rect = GetItemRect(index);
 	contents->ClearRect(rect);
 
-	StringView text;
-
-	if (HasValidEntry()) {
-		text = game_directories[index].GetReadableName();
-	}
-
+	StringView text = game_directories[index].GetReadableName();
 	contents->TextDraw(rect.x, rect.y, Font::ColorDefault, text);
 }