Skip to content

Commit ab5c39a

Browse files
committed
Fix some VFS issues
1 parent 2893b42 commit ab5c39a

9 files changed

Lines changed: 116 additions & 35 deletions

File tree

Utilities/File.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,19 @@ namespace fs
198198
// Return "path" plus an ending delimiter (if missing) if "path" is an existing directory. Otherwise, an empty string
199199
std::string get_path_if_dir(const std::string& path);
200200

201+
// Check whether the path is the bare name of a drive (e.g. "E:"), that is a root path whose trailing delimiter was
202+
// trimmed: such a path does not point to the root of the drive but to the current directory of that drive, so the
203+
// delimiter must be restored before using or storing it.
204+
// NOTE: always false on the other platforms, where a name ending with ':' is a regular path component
205+
inline bool is_drive_name([[maybe_unused]] std::string_view path)
206+
{
207+
#ifdef _WIN32
208+
return !path.empty() && path.back() == ':';
209+
#else
210+
return false;
211+
#endif
212+
}
213+
201214
// Get file information
202215
bool get_stat(const std::string& path, stat_t& info);
203216

rpcs3/Emu/System.cpp

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
#include "util/sysinfo.hpp"
5050

5151
#include <memory>
52-
#include <regex>
5352
#include <shared_mutex>
5453

5554
#include "Utilities/JIT.h"
@@ -4412,16 +4411,30 @@ u32 Emulator::AddGamesFromDir(std::string path)
44124411

44134412
fmt::trim_back(path, fs::delim);
44144413

4414+
// Don't write "games.yml" on each added game: it is saved once, at the end of the scan.
4415+
// NOTE: this function is recursive, so the previous value is restored instead of being forced back to enabled,
4416+
// otherwise a nested scan would re-enable the write for the remaining part of the outer one
4417+
const bool save_on_dirty = m_games_config.is_save_on_dirty();
44154418
m_games_config.set_save_on_dirty(false);
44164419

4420+
// A game was found on a path if it has just been added or if it was already registered
4421+
const auto game_found = [](game_boot_result error)
4422+
{
4423+
return error == game_boot_result::no_errors || error == game_boot_result::already_added;
4424+
};
4425+
44174426
// search for a game on the provided path first (game on ISO file or on folder type)
4418-
if (const game_boot_result error = AddGame(path); error == game_boot_result::no_errors)
4427+
const game_boot_result path_error = AddGame(path);
4428+
4429+
if (path_error == game_boot_result::no_errors)
44194430
{
44204431
games_added++;
44214432
}
44224433

4423-
// search for games on subfolders only if not nested inside a discovered game folder
4424-
if (games_added == 0)
4434+
// search for games on subfolders only if not nested inside a discovered game folder, otherwise the same title
4435+
// would be registered again through a different path (e.g. the root of a BD drive "E:/" is registered as a raw
4436+
// device, its subfolder "E:/PS3_GAME" would register it again as a disc folder)
4437+
if (!game_found(path_error))
44254438
{
44264439
std::vector<fs::dir_entry> entries;
44274440

@@ -4447,16 +4460,20 @@ u32 Emulator::AddGamesFromDir(std::string path)
44474460

44484461
const std::string dir_path = path + "/" + dir_entry.name;
44494462

4450-
if (!dir_entry.is_directory && !is_iso_file(dir_path))
4463+
// The outcome is handed over to "AddGame()" so that the ISO is not recognized twice: each check
4464+
// reads the volume descriptor, which is a physical read when the path points to an optical drive
4465+
const bool is_iso = !dir_entry.is_directory && is_iso_file(dir_path);
4466+
4467+
if (!dir_entry.is_directory && !is_iso)
44514468
{
44524469
continue;
44534470
}
44544471

4455-
if (const game_boot_result error = AddGame(dir_path); error == game_boot_result::no_errors)
4472+
if (const game_boot_result error = AddGame(dir_path, is_iso); error == game_boot_result::no_errors)
44564473
{
44574474
games_added++;
44584475
}
4459-
else if (g_cfg.misc.use_recursive_scan)
4476+
else if (!game_found(error) && g_cfg.misc.use_recursive_scan)
44604477
{
44614478
games_added += AddGamesFromDir(dir_path);
44624479
}
@@ -4471,24 +4488,25 @@ u32 Emulator::AddGamesFromDir(std::string path)
44714488
});
44724489
}
44734490

4474-
m_games_config.set_save_on_dirty(true);
4491+
m_games_config.set_save_on_dirty(save_on_dirty);
44754492

4476-
if (m_games_config.is_dirty() && !m_games_config.save())
4493+
// Flush the changes only when the outermost scan is done
4494+
if (save_on_dirty && m_games_config.is_dirty() && !m_games_config.save())
44774495
{
44784496
sys_log.error("Failed to save games.yml after adding games");
44794497
}
44804498

44814499
return games_added;
44824500
}
44834501

4484-
game_boot_result Emulator::AddGame(std::string path)
4502+
game_boot_result Emulator::AddGame(std::string path, bool is_iso)
44854503
{
44864504
fmt::trim_back(path, fs::delim);
44874505

44884506
// Handle files directly
44894507
if (!fs::is_dir(path) || fs::get_optical_raw_device(path))
44904508
{
4491-
return AddGameToYml(path);
4509+
return AddGameToYml(path, is_iso);
44924510
}
44934511

44944512
game_boot_result result = game_boot_result::nothing_to_boot;
@@ -4509,7 +4527,7 @@ game_boot_result Emulator::AddGame(std::string path)
45094527
continue;
45104528
}
45114529

4512-
if (entry.is_directory && std::regex_match(entry.name, std::regex("^PS3_GM[[:digit:]]{2}$")))
4530+
if (entry.is_directory && rpcs3::utils::is_ps3_gm_dir_name(entry.name))
45134531
{
45144532
const std::string elf = path + "/" + entry.name + "/USRDIR/EBOOT.BIN";
45154533

@@ -4530,7 +4548,7 @@ game_boot_result Emulator::AddGame(std::string path)
45304548
return result;
45314549
}
45324550

4533-
game_boot_result Emulator::AddGameToYml(std::string path)
4551+
game_boot_result Emulator::AddGameToYml(std::string path, bool is_iso)
45344552
{
45354553
fmt::trim_back(path, fs::delim);
45364554

@@ -4559,7 +4577,9 @@ game_boot_result Emulator::AddGameToYml(std::string path)
45594577
}
45604578

45614579
std::unique_ptr<iso_archive> archive;
4562-
if (is_iso_file(path))
4580+
4581+
// Skip the check if the caller already recognized the path as an ISO: it would read the volume descriptor again
4582+
if (is_iso || is_iso_file(path))
45634583
{
45644584
archive = std::make_unique<iso_archive>(path);
45654585

@@ -4826,7 +4846,7 @@ void Emulator::GetBdvdDir(std::string& bdvd_dir, std::string& sfb_dir, std::stri
48264846
continue;
48274847
}
48284848

4829-
if (dir_name == "PS3_GAME"sv || std::regex_match(dir_name.begin(), dir_name.end(), std::regex("^PS3_GM[[:digit:]]{2}$")))
4849+
if (dir_name == "PS3_GAME"sv || rpcs3::utils::is_ps3_gm_dir_name(dir_name))
48304850
{
48314851
if (IsValidSfb(parent_dir + "/PS3_DISC.SFB"))
48324852
{

rpcs3/Emu/System.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,11 @@ class Emulator final
496496

497497
std::set<std::string> GetGameDirs() const;
498498
u32 AddGamesFromDir(std::string path);
499-
game_boot_result AddGame(std::string path);
500-
game_boot_result AddGameToYml(std::string path);
499+
500+
// "is_iso" tells the caller has already recognized the path as an ISO file (or as a raw device holding a disc):
501+
// checking it again would read its volume descriptor once more, which is a physical read on an optical drive
502+
game_boot_result AddGame(std::string path, bool is_iso = false);
503+
game_boot_result AddGameToYml(std::string path, bool is_iso = false);
501504
u32 RemoveGamesFromDir(const std::string& games_dir, const std::vector<std::string>& serials_to_remove_from_yml = {}, bool save_on_disk = true);
502505
u32 RemoveGames(const std::vector<std::string>& title_id_list, bool save_on_disk = true);
503506
game_boot_result RemoveGameFromYml(const std::string& title_id);

rpcs3/Emu/games_config.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ games_config::result games_config::add_game(const std::string& key, const std::s
5555
return add_game(key, iso_dev->get_loaded_iso());
5656
}
5757

58+
if (fs::is_drive_name(path))
59+
{
60+
// Restore the trailing delimiter trimmed by the caller if the game is on the root of a raw device (e.g. "E:/"),
61+
// which is also the form provided by "rpcs3::utils::get_games_dir()" and the one built by "GetBdvdDir()", so
62+
// that the same title is always registered with the very same path
63+
return add_game(key, path + '/');
64+
}
65+
5866
std::lock_guard lock(m_mutex);
5967

6068
// Access or create node if does not exist

rpcs3/Emu/games_config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class games_config
1010
virtual ~games_config();
1111

1212
void set_save_on_dirty(bool enabled) { m_save_on_dirty = enabled; }
13+
bool is_save_on_dirty() const { return m_save_on_dirty; }
1314

1415
const std::map<std::string, std::string> get_games() const;
1516
bool is_dirty() const { return m_dirty; }

rpcs3/Emu/system_utils.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "util/types.hpp"
44
#include <string>
5+
#include <string_view>
56
#include <set>
67
#include <vector>
78

@@ -80,6 +81,13 @@ namespace rpcs3::utils
8081
bool verify_c00_unlock_edat(const std::string_view& content_id, bool fast = false);
8182
std::string get_sfo_dir_from_game_path(const std::string& game_path, const std::string& title_id = "");
8283

84+
// Check whether "name" is the name of an additional disc game directory (e.g. "PS3_GM01"), the counterpart of "PS3_GAME".
85+
// NOTE: equivalent to the "^PS3_GM[[:digit:]]{2}$" regular expression, without building one for each checked name
86+
inline bool is_ps3_gm_dir_name(std::string_view name)
87+
{
88+
return name.size() == 8 && name.starts_with("PS3_GM") && name[6] >= '0' && name[6] <= '9' && name[7] >= '0' && name[7] <= '9';
89+
}
90+
8391
std::string get_custom_config_dir();
8492
std::string get_custom_config_path(const std::string& identifier);
8593

rpcs3/Loader/ISO.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,15 @@ static void* get_aligned_buf()
6060

6161
static bool is_iso_file(iso_file& file, u64* size = nullptr)
6262
{
63-
if (!file || file.size() < 32768ULL + 6)
63+
// The standard identifier ("CD001") follows the type of the first volume descriptor
64+
if (!file || file.size() < ISO_DESCRIPTORS_OFFSET + 6)
6465
{
6566
return false;
6667
}
6768

6869
char magic[5];
6970

70-
if (file.read_at(32768ULL + 1, magic, 5) != 5)
71+
if (file.read_at(ISO_DESCRIPTORS_OFFSET + 1, magic, 5) != 5)
7172
{
7273
return false;
7374
}
@@ -1029,18 +1030,30 @@ iso_archive::iso_archive(const std::string& path)
10291030
// "m_path" is updated with the raw device path in case "path" points to a BD drive
10301031
fs::get_optical_raw_device(path, &m_path);
10311032

1032-
if (!is_iso_file(m_path))
1033+
// NOTE: the file is opened once here and then handed over to the parsing below. Recognizing the ISO through its
1034+
// path (i.e. "is_iso_file(m_path)") would open it and read its volume descriptor a second time, which is a
1035+
// physical read when the path points to an optical drive
1036+
auto file = std::make_unique<iso_file>(m_path);
1037+
1038+
if (!is_iso_file(*file))
10331039
{
10341040
iso_log.error("iso_archive: Failed to recognize ISO file: '%s'", path);
10351041
invalidate();
10361042
return;
10371043
}
10381044

1039-
fs::file iso_file(std::make_unique<iso_file>(m_path));
1045+
// NOTE: "is_iso_file()" reads through "read_at()", which does not move the position, so the file is still at its
1046+
// beginning here
1047+
fs::file iso_file(std::move(file));
10401048

10411049
u8 descriptor_type = -2;
10421050
bool use_ucs2_decoding = false;
10431051

1052+
// Skip the system area: scanning it sector by sector would read 16 sectors (a physical read each, on an optical
1053+
// drive) only to find boot data, which could even be mistaken for a volume descriptor.
1054+
// NOTE: "is_iso_file()" above already verified the standard identifier is right here
1055+
iso_file.seek(ISO_DESCRIPTORS_OFFSET);
1056+
10441057
do
10451058
{
10461059
const auto descriptor_start = iso_file.pos();

rpcs3/Loader/ISO.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ void unload_iso();
1515

1616
constexpr u64 ISO_SECTOR_SIZE = 2048;
1717

18+
// The first 16 sectors are the system area (boot data): the volume descriptors always start right after it (ECMA-119)
19+
constexpr u64 ISO_DESCRIPTORS_OFFSET = ISO_SECTOR_SIZE * 16;
20+
1821
/*
1922
- Hijacked the "iso_archive::iso_archive" method to test if the ".iso" file is encrypted and sets a flag.
2023
The flag is set according to the first matching encryption type found following the order below:

rpcs3/rpcs3qt/game_list_frame.cpp

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
#include <algorithm>
2626
#include <memory>
27-
#include <regex>
2827
#include <unordered_set>
2928

3029
#include <QtConcurrent>
@@ -596,13 +595,24 @@ void game_list_frame::OnParsingFinished()
596595
const auto add_game = [this, localized, localized_title, localized_icon, localized_movie, dev_flash, game_icon_path, _hdd,
597596
cat_unknown_localized = localized->category.unknown.toStdString(), cat_unknown = cat::cat_unknown.toStdString(),
598597
play_hover_movies = m_play_hover_movies, play_hover_music = m_play_hover_music, show_custom_icons = m_show_custom_icons]
599-
(const std::string& dir_or_elf, const std::string& game_dir = "PS3_GAME")
598+
(const std::string& dir_or_elf, const std::string& game_dir = "PS3_GAME", const std::shared_ptr<iso_archive>& shared_archive = {})
600599
{
601-
std::unique_ptr<iso_archive> archive;
600+
std::shared_ptr<iso_archive> archive;
602601
iso_metadata_cache_entry cache_entry{};
603602
bool is_raw_device = false;
604-
const bool is_archive = is_iso_file(dir_or_elf, nullptr, &is_raw_device);
603+
bool is_archive = true;
605604
std::string iso_cache_key;
605+
606+
if (shared_archive)
607+
{
608+
// The caller provides the archive only for a path it has already recognized: just the raw device flag is
609+
// still needed here, and it is answered without reading the volume descriptor of the disc
610+
is_raw_device = fs::get_optical_raw_device(dir_or_elf);
611+
}
612+
else
613+
{
614+
is_archive = is_iso_file(dir_or_elf, nullptr, &is_raw_device);
615+
}
606616

607617
if (is_archive)
608618
{
@@ -611,7 +621,9 @@ void game_list_frame::OnParsingFinished()
611621
// when no valid cache entry exists for this ISO path + mtime
612622
if (is_raw_device || !iso_cache::load(dir_or_elf, iso_cache_key, cache_entry))
613623
{
614-
archive = std::make_unique<iso_archive>(dir_or_elf);
624+
// Reuse the archive the caller has already built for this path: constructing another one walks the
625+
// whole file system of the disc again (and a raw device never uses the cache)
626+
archive = shared_archive ? shared_archive : std::make_shared<iso_archive>(dir_or_elf);
615627
if (!archive->is_valid()) return;
616628
}
617629

@@ -648,7 +660,7 @@ void game_list_frame::OnParsingFinished()
648660
if (!psf_valid)
649661
{
650662
game_list_log.warning("Cached psf for iso not valid: '%s'", game.info.path);
651-
archive = std::make_unique<iso_archive>(dir_or_elf);
663+
archive = shared_archive ? shared_archive : std::make_shared<iso_archive>(dir_or_elf);
652664
if (!archive->is_valid()) return;
653665

654666
cache_entry = {}; // Reset so the cache gets rewritten after scan.
@@ -896,7 +908,7 @@ void game_list_frame::OnParsingFinished()
896908
continue;
897909
}
898910

899-
if (entry.name == "PS3_GAME" || std::regex_match(entry.name, std::regex("^PS3_GM[[:digit:]]{2}$")))
911+
if (entry.name == "PS3_GAME" || rpcs3::utils::is_ps3_gm_dir_name(entry.name))
900912
{
901913
push_path(path + "/" + entry.name, legit_paths);
902914
}
@@ -924,11 +936,11 @@ void game_list_frame::OnParsingFinished()
924936
return;
925937
}
926938

927-
iso_archive archive(entry.path);
928-
if (!archive.is_valid()) return;
939+
// Shared with "add_game()" below, so that the file system of the disc is walked only once
940+
const auto archive = std::make_shared<iso_archive>(entry.path);
941+
if (!archive->is_valid()) return;
929942

930-
const iso_fs_node& root = archive.root();
931-
const std::regex ps3_gm_regex("^PS3_GM[[:digit:]]{2}$");
943+
const iso_fs_node& root = archive->root();
932944

933945
for (const auto& child : root.children)
934946
{
@@ -942,15 +954,15 @@ void game_list_frame::OnParsingFinished()
942954
}
943955

944956
const std::string& name = child->metadata.name;
945-
if (name == "PS3_GAME" || std::regex_match(name, ps3_gm_regex))
957+
if (name == "PS3_GAME" || rpcs3::utils::is_ps3_gm_dir_name(name))
946958
{
947959
subdirs.push_back(name);
948-
add_game(entry.path, name);
960+
add_game(entry.path, name, archive);
949961
}
950962
}
951963
if (subdirs.empty())
952964
{
953-
add_game(entry.path);
965+
add_game(entry.path, "PS3_GAME", archive);
954966
subdirs.push_back("PS3_GAME");
955967
}
956968
if (!m_refresh_watcher.isCanceled())

0 commit comments

Comments
 (0)