Skip to content

Commit b212935

Browse files
authored
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`
1 parent e05d359 commit b212935

12 files changed

Lines changed: 666 additions & 210 deletions

File tree

Utilities/File.cpp

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ static std::unique_ptr<wchar_t[]> to_wchar(std::string_view source)
3838
// Buffer for max possible output length
3939
std::unique_ptr<wchar_t[]> buffer(new wchar_t[buf_size + 8 + 32768]);
4040

41+
// If path points to an optical raw device, copy it AS IS
42+
if (fs::is_optical_raw_device(std::string(source)))
43+
{
44+
ensure(MultiByteToWideChar(CP_UTF8, 0, source.data(), size, buffer.get() + 32768, size)); // "to_wchar"
45+
46+
// Canonicalize wide path (replace '/', ".", "..", \\ repetitions, etc)
47+
ensure(GetFullPathNameW(buffer.get() + 32768, 32768, buffer.get(), nullptr) - 1 < 32768 - 1); // "to_wchar"
48+
49+
return buffer;
50+
}
51+
4152
// Prepend wide path prefix (4 characters)
4253
std::memcpy(buffer.get() + 32768, L"\\\\\?\\", 4 * sizeof(wchar_t));
4354

@@ -400,11 +411,12 @@ namespace fs
400411
class windows_file final : public file_base
401412
{
402413
HANDLE m_handle;
414+
bool m_raw_device;
403415
atomic_t<u64> m_pos {0};
404416

405417
public:
406-
windows_file(HANDLE handle)
407-
: m_handle(handle)
418+
windows_file(HANDLE handle, bool raw_device = false)
419+
: m_handle(handle), m_raw_device(raw_device)
408420
{
409421
}
410422

@@ -564,11 +576,20 @@ namespace fs
564576

565577
u64 size() override
566578
{
567-
// NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso).
568-
LARGE_INTEGER size;
569-
ensure(GetFileSizeEx(m_handle, &size)); // "file::size"
579+
if (!m_raw_device)
580+
{
581+
// NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso).
582+
LARGE_INTEGER size;
583+
584+
ensure(GetFileSizeEx(m_handle, &size)); // "file::size"
585+
return size.QuadPart;
586+
}
570587

571-
return size.QuadPart;
588+
// For a raw device, we need to use DeviceIoControl.
589+
DISK_GEOMETRY_EX geometry;
590+
591+
ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr));
592+
return geometry.DiskSize.QuadPart;
572593
}
573594

574595
native_handle get_handle() override
@@ -1091,6 +1112,68 @@ bool fs::is_symlink(const std::string& path)
10911112
return true;
10921113
}
10931114

1115+
bool fs::is_optical_raw_device(const std::string& path)
1116+
{
1117+
#ifdef _WIN32
1118+
if (path.starts_with("\\\\.\\"))
1119+
{
1120+
return true;
1121+
}
1122+
1123+
return false;
1124+
#endif
1125+
return false;
1126+
}
1127+
1128+
bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device)
1129+
{
1130+
if (fs::is_optical_raw_device(path))
1131+
{
1132+
if (raw_device)
1133+
{
1134+
*raw_device = path;
1135+
}
1136+
1137+
return true;
1138+
}
1139+
1140+
#ifdef _WIN32
1141+
constexpr u32 BUF_SIZE = 1000;
1142+
WCHAR drive_list[BUF_SIZE] = {0};
1143+
1144+
// GetLogicalDriveStrings() returns a double-null terminated list of null-terminated strings.
1145+
// E.g. A:\<nul>B:\<nul>C:\<nul><nul>
1146+
const DWORD copied = GetLogicalDriveStrings(BUF_SIZE, drive_list);
1147+
1148+
if (copied == 0 || copied > BUF_SIZE)
1149+
{
1150+
return false;
1151+
}
1152+
1153+
for (const WCHAR* drive = drive_list; drive && *drive; drive += wcslen(drive) + 1)
1154+
{
1155+
if (GetDriveType(drive) == DRIVE_CDROM)
1156+
{
1157+
const std::wstring ws(drive);
1158+
const std::string s = std::string(ws.begin(), ws.end() - 1);
1159+
1160+
if (path.starts_with(s))
1161+
{
1162+
if (raw_device)
1163+
{
1164+
*raw_device = "\\\\.\\" + s;
1165+
}
1166+
1167+
return true;
1168+
}
1169+
}
1170+
}
1171+
1172+
return false;
1173+
#endif
1174+
return false;
1175+
}
1176+
10941177
bool fs::statfs(const std::string& path, fs::device_stat& info)
10951178
{
10961179
if (auto device = get_virtual_device(path))
@@ -1658,9 +1741,18 @@ fs::file::file(const std::string& path, bs_t<open_mode> mode)
16581741
return;
16591742
}
16601743

1744+
// If path points to an optical raw device, complete the file opening
1745+
// (the following GetFileInformationByHandle() would always fail on a raw device).
1746+
if (is_optical_raw_device(path))
1747+
{
1748+
m_file = std::make_unique<windows_file>(handle, true);
1749+
return;
1750+
}
1751+
16611752
// Check if the handle is actually valid.
16621753
// This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION).
16631754
BY_HANDLE_FILE_INFORMATION info{};
1755+
16641756
if (!GetFileInformationByHandle(handle, &info))
16651757
{
16661758
const DWORD last_error = GetLastError();
@@ -1671,7 +1763,7 @@ fs::file::file(const std::string& path, bs_t<open_mode> mode)
16711763
g_tls_error = fs::error::isdir;
16721764
return;
16731765
}
1674-
1766+
16751767
g_tls_error = to_error(last_error);
16761768
return;
16771769
}

Utilities/File.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ namespace fs
213213
// Check whether the path points to an existing symlink
214214
bool is_symlink(const std::string& path);
215215

216+
// Check whether the path points to a raw device
217+
bool is_optical_raw_device(const std::string& path);
218+
219+
// Check whether the path points to an optical drive. If so, provide the raw device in "raw_device" if requested
220+
bool get_optical_raw_device(const std::string& path, std::string* raw_device = nullptr);
221+
216222
// Get filesystem information
217223
bool statfs(const std::string& path, device_stat& info);
218224

rpcs3/Emu/System.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ void Emulator::Init()
638638

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

641-
if (make_path_verbose(games_common_dir, true))
641+
if (!is_iso_file(games_common_dir) && make_path_verbose(games_common_dir, true))
642642
{
643643
fs::write_file(games_common_dir + "/Disc Games Can Be Put Here For Automatic Detection.txt", fs::create + fs::excl + fs::write, ""s);
644644

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

987987
// Handle files and special paths inside Load unmodified
988-
if (direct || !fs::is_dir(path))
988+
if (direct || !fs::is_dir(path) || fs::get_optical_raw_device(path))
989989
{
990990
m_path = path;
991991

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

1204-
launching_from_disc_archive = is_file_iso(disc_info);
1204+
launching_from_disc_archive = is_iso_file(disc_info);
12051205

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

@@ -1218,7 +1218,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
12181218
// Load /dev_bdvd/ from game list if available
12191219
if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty())
12201220
{
1221-
if (is_file_iso(game_path))
1221+
if (is_iso_file(game_path))
12221222
{
12231223
game_path = iso_device::virtual_device_name + "/PS3_GAME/./";
12241224
}
@@ -1389,7 +1389,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
13891389
title_path = std::move(game_path);
13901390
}
13911391

1392-
if (is_file_iso(title_path))
1392+
if (is_iso_file(title_path))
13931393
{
13941394
m_path = std::move(title_path);
13951395
ok = true;
@@ -1480,7 +1480,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
14801480
}
14811481

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

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

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

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

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

4249-
if (!dir_entry.is_directory && !is_file_iso(dir_path))
4249+
if (!dir_entry.is_directory && !is_iso_file(dir_path))
42504250
{
42514251
continue;
42524252
}
@@ -4283,7 +4283,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path)
42834283
game_boot_result Emulator::AddGame(const std::string& path)
42844284
{
42854285
// Handle files directly
4286-
if (!fs::is_dir(path))
4286+
if (!fs::is_dir(path) || fs::get_optical_raw_device(path))
42874287
{
42884288
return AddGameToYml(path);
42894289
}
@@ -4354,7 +4354,7 @@ game_boot_result Emulator::AddGameToYml(const std::string& path)
43544354
}
43554355

43564356
std::unique_ptr<iso_archive> archive;
4357-
if (is_file_iso(path))
4357+
if (is_iso_file(path))
43584358
{
43594359
archive = std::make_unique<iso_archive>(path);
43604360
}

0 commit comments

Comments
 (0)