Add support to play from a BD Drive (currently only on Windows) (#18648)

Follow up of #18345 to add support (currently only on `Windows`; see
notes below) for playing a PS3 disc game directly from a Blu-Ray Disc
Drive.

### HOW IT WORKS:
- The BD drive can be added as any other game so from `VFS games` or
from `Add Games` menu. In case it is selected from `VFS games`, any
attempt to write files is discarded, e.g. file `Disc Games Can Be Put
Here For Automatic Detection.txt`
- It scans the default redump keys folder `<rpcs3>/data/redump` (it
currently needs to be manually created due it is not yet provided by
rpcs3 installation) to find a matching decryption key

### NOTES:
- Support is currently provided on `Windows` where I can fully test it.
I cannot test under other OS. However, the additions needed for the
other OS are limited only on `fs::file.h/cpp`. In particular inside the
following new functions:
  - `bool is_optical_raw_device(const std::string& path);`
- `bool get_optical_raw_device(const std::string& path, std::string*
raw_device = nullptr);`
- Icons etc. are always refreshed (ISO cache cannot be used due `mtime`
on raw device is not available and any cache check would always fail)
- Code in `ISO.h/cpp` needed some rework to properly manage a read on a
raw device (alignment on offset, size and memory is mandatory). The BD
drive needs to be detected as a file, not as a folder

### MINOR FIXES:
- Fixed wrong specifier used in logging on `ISO.h/cpp`
This commit is contained in:
Antonino Di Guardo 2026-04-29 04:42:49 +02:00 committed by GitHub
parent e05d359721
commit b212935c70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 668 additions and 212 deletions

View file

@ -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)))
{
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));
@ -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)
{
}
@ -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;
return size.QuadPart;
ensure(GetFileSizeEx(m_handle, &size)); // "file::size"
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
@ -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};
// 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))
@ -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();
@ -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;
}