Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 99 additions & 7 deletions Utilities/File.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ static std::unique_ptr<wchar_t[]> to_wchar(std::string_view source)
// Buffer for max possible output length
std::unique_ptr<wchar_t[]> buffer(new wchar_t[buf_size + 8 + 32768]);

// If path points to an optical raw device, copy it AS IS
if (fs::is_optical_raw_device(std::string(source)))
Comment thread
digant73 marked this conversation as resolved.
{
ensure(MultiByteToWideChar(CP_UTF8, 0, source.data(), size, buffer.get() + 32768, size)); // "to_wchar"

// Canonicalize wide path (replace '/', ".", "..", \\ repetitions, etc)
ensure(GetFullPathNameW(buffer.get() + 32768, 32768, buffer.get(), nullptr) - 1 < 32768 - 1); // "to_wchar"

return buffer;
}

// Prepend wide path prefix (4 characters)
std::memcpy(buffer.get() + 32768, L"\\\\\?\\", 4 * sizeof(wchar_t));

Expand Down Expand Up @@ -400,11 +411,12 @@ namespace fs
class windows_file final : public file_base
{
HANDLE m_handle;
bool m_raw_device;
atomic_t<u64> m_pos {0};

public:
windows_file(HANDLE handle)
: m_handle(handle)
windows_file(HANDLE handle, bool raw_device = false)
: m_handle(handle), m_raw_device(raw_device)
{
}

Expand Down Expand Up @@ -564,11 +576,20 @@ namespace fs

u64 size() override
{
// NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso).
LARGE_INTEGER size;
ensure(GetFileSizeEx(m_handle, &size)); // "file::size"
if (!m_raw_device)
{
// NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso).
LARGE_INTEGER size;

ensure(GetFileSizeEx(m_handle, &size)); // "file::size"
return size.QuadPart;
}

return size.QuadPart;
// For a raw device, we need to use DeviceIoControl.
DISK_GEOMETRY_EX geometry;

ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr));
return geometry.DiskSize.QuadPart;
}

native_handle get_handle() override
Expand Down Expand Up @@ -1091,6 +1112,68 @@ bool fs::is_symlink(const std::string& path)
return true;
}

bool fs::is_optical_raw_device(const std::string& path)
{
#ifdef _WIN32
if (path.starts_with("\\\\.\\"))
{
return true;
}

return false;
#endif
return false;
}

bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device)
{
if (fs::is_optical_raw_device(path))
{
if (raw_device)
{
*raw_device = path;
}

return true;
}

#ifdef _WIN32
constexpr u32 BUF_SIZE = 1000;
WCHAR drive_list[BUF_SIZE] = {0};
Comment thread
digant73 marked this conversation as resolved.

// GetLogicalDriveStrings() returns a double-null terminated list of null-terminated strings.
// E.g. A:\<nul>B:\<nul>C:\<nul><nul>
const DWORD copied = GetLogicalDriveStrings(BUF_SIZE, drive_list);

if (copied == 0 || copied > BUF_SIZE)
{
return false;
}

for (const WCHAR* drive = drive_list; drive && *drive; drive += wcslen(drive) + 1)
{
if (GetDriveType(drive) == DRIVE_CDROM)
{
const std::wstring ws(drive);
const std::string s = std::string(ws.begin(), ws.end() - 1);

if (path.starts_with(s))
{
if (raw_device)
{
*raw_device = "\\\\.\\" + s;
}

return true;
}
}
}

return false;
#endif
return false;
}

bool fs::statfs(const std::string& path, fs::device_stat& info)
{
if (auto device = get_virtual_device(path))
Expand Down Expand Up @@ -1658,9 +1741,18 @@ fs::file::file(const std::string& path, bs_t<open_mode> mode)
return;
}

// If path points to an optical raw device, complete the file opening
// (the following GetFileInformationByHandle() would always fail on a raw device).
if (is_optical_raw_device(path))
{
m_file = std::make_unique<windows_file>(handle, true);
return;
}

// Check if the handle is actually valid.
// This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION).
BY_HANDLE_FILE_INFORMATION info{};

if (!GetFileInformationByHandle(handle, &info))
{
const DWORD last_error = GetLastError();
Expand All @@ -1671,7 +1763,7 @@ fs::file::file(const std::string& path, bs_t<open_mode> mode)
g_tls_error = fs::error::isdir;
return;
}

g_tls_error = to_error(last_error);
return;
}
Expand Down
6 changes: 6 additions & 0 deletions Utilities/File.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ namespace fs
// Check whether the path points to an existing symlink
bool is_symlink(const std::string& path);

// Check whether the path points to a raw device
bool is_optical_raw_device(const std::string& path);
Comment thread
digant73 marked this conversation as resolved.

// Check whether the path points to an optical drive. If so, provide the raw device in "raw_device" if requested
bool get_optical_raw_device(const std::string& path, std::string* raw_device = nullptr);
Comment thread
digant73 marked this conversation as resolved.

// Get filesystem information
bool statfs(const std::string& path, device_stat& info);

Expand Down
22 changes: 11 additions & 11 deletions rpcs3/Emu/System.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ void Emulator::Init()

const std::string games_common_dir = g_cfg_vfs.get(g_cfg_vfs.games_dir, emu_dir);

if (make_path_verbose(games_common_dir, true))
if (!is_iso_file(games_common_dir) && make_path_verbose(games_common_dir, true))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a point in this

@digant73 digant73 Apr 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise a Fatal error (trying to write on a read only device) is logged. You can see it in current build simply setting a VFS games to a dvd drive.
Or eventually the log level could be converted to Error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would you do that? That's user error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's to provide dynamic scan (and the only available feature is based on VFS) also for BD drive. IMO, dynamic list is very useful (I basically use only that, so I can display some games instead of others). Of course, the Fatal error is not impacting the functionality (it will work without any issue). I simply suppressed that misleading error

{
fs::write_file(games_common_dir + "/Disc Games Can Be Put Here For Automatic Detection.txt", fs::create + fs::excl + fs::write, ""s);

Expand Down Expand Up @@ -985,7 +985,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
m_db_config = db_config;

// Handle files and special paths inside Load unmodified
if (direct || !fs::is_dir(path))
if (direct || !fs::is_dir(path) || fs::get_optical_raw_device(path))
{
m_path = path;

Expand Down Expand Up @@ -1201,7 +1201,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
std::string disc_info;
m_ar->serialize(argv.emplace_back(), disc_info, klic.emplace_back(), m_game_dir, hdd1);

launching_from_disc_archive = is_file_iso(disc_info);
launching_from_disc_archive = is_iso_file(disc_info);

sys_log.notice("Savestate: is iso archive = %d ('%s')", launching_from_disc_archive, disc_info);

Expand All @@ -1218,7 +1218,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
// Load /dev_bdvd/ from game list if available
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
{
if (is_file_iso(game_path))
if (is_iso_file(game_path))
{
game_path = iso_device::virtual_device_name + "/PS3_GAME/./";
}
Expand Down Expand Up @@ -1389,7 +1389,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
title_path = std::move(game_path);
}

if (is_file_iso(title_path))
if (is_iso_file(title_path))
{
m_path = std::move(title_path);
ok = true;
Expand Down Expand Up @@ -1480,7 +1480,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
}

const std::string resolved_path = GetCallbacks().resolve_path(m_path);
if (!launching_from_disc_archive && is_file_iso(m_path))
if (!launching_from_disc_archive && is_iso_file(m_path))
{
sys_log.notice("Loading iso archive '%s'", m_path);

Expand Down Expand Up @@ -1933,7 +1933,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
// Load /dev_bdvd/ from game list if available
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
{
if (is_file_iso(game_path))
if (is_iso_file(game_path))
{
sys_log.notice("Loading iso archive for patch ('%s')", game_path);

Expand Down Expand Up @@ -4237,7 +4237,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path)
// search direct subdirectories, that way we can drop one folder containing all games
for (; path_it != entries.end(); ++path_it)
{
auto dir_entry = std::move(*path_it);
const auto dir_entry = std::move(*path_it);

if (dir_entry.name == "." || dir_entry.name == "..")
{
Expand All @@ -4246,7 +4246,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path)

const std::string dir_path = path + '/' + dir_entry.name;

if (!dir_entry.is_directory && !is_file_iso(dir_path))
if (!dir_entry.is_directory && !is_iso_file(dir_path))
{
continue;
}
Expand Down Expand Up @@ -4283,7 +4283,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path)
game_boot_result Emulator::AddGame(const std::string& path)
{
// Handle files directly
if (!fs::is_dir(path))
if (!fs::is_dir(path) || fs::get_optical_raw_device(path))
{
return AddGameToYml(path);
}
Expand Down Expand Up @@ -4354,7 +4354,7 @@ game_boot_result Emulator::AddGameToYml(const std::string& path)
}

std::unique_ptr<iso_archive> archive;
if (is_file_iso(path))
if (is_iso_file(path))
{
archive = std::make_unique<iso_archive>(path);
}
Expand Down
Loading