Handle lowercase game path

This commit is contained in:
Manolis Ragkousis
2026-09-30 17:44:40 +03:00
parent 93cf112eed
commit a6aac5a626
4 changed files with 115 additions and 5 deletions
+2
View File
@@ -322,6 +322,8 @@ private:
std::unordered_map<std::uint32_t, NativeFastPath> native_fast_paths_;
std::vector<const HleFunction *> import_bindings_;
std::filesystem::path game_root_;
// Cached when the extract case differs.
mutable std::unordered_map<std::string, std::filesystem::path> folded_paths_;
bool stopped_{};
std::string stop_reason_;
bool hle_histogram_enabled_{};
+12 -3
View File
@@ -200,6 +200,15 @@ struct DirectoryHandle {
std::size_t index{};
};
// ELF directory names are uppercase.
std::string disc_directory_name(const std::filesystem::path &path) {
std::string name = path.filename().string();
for (char &ch : name) {
if (ch >= 'a' && ch <= 'z') ch = static_cast<char>(ch - 'a' + 'A');
}
return name;
}
struct RawSectorFile {
std::uint64_t base{};
std::uint64_t size{};
@@ -3747,7 +3756,7 @@ void install_profile(psprecomp::Runtime &runtime, std::uint32_t user_arena_start
DirectoryHandle handle;
for (const auto &entry : std::filesystem::directory_iterator(native)) handle.entries.push_back(entry);
std::sort(handle.entries.begin(), handle.entries.end(), [](const auto &a, const auto &b) {
return a.path().filename().string() < b.path().filename().string();
return disc_directory_name(a.path()) < disc_directory_name(b.path());
});
const auto fd = file_table.next_fd++;
file_table.directories.emplace(fd, std::move(handle));
@@ -3781,13 +3790,13 @@ void install_profile(psprecomp::Runtime &runtime, std::uint32_t user_arena_start
rt.memory().store32(dirent + 12u, static_cast<std::uint32_t>(disc_file->size >> 32u));
rt.memory().store32(dirent + 0x40u, disc_file->start_sector);
if (std::getenv("PSPRECOMP_IO_DIAG") != nullptr) {
std::cerr << "[io] sceIoDread file=\"" << entry.path().filename().string()
std::cerr << "[io] sceIoDread file=\"" << disc_directory_name(entry.path())
<< "\" sector=" << disc_file->start_sector
<< " size=" << disc_file->size << "\n";
}
}
}
const std::string name = entry.path().filename().string();
const std::string name = disc_directory_name(entry.path());
std::vector<std::uint8_t> bytes(name.begin(), name.end());
bytes.push_back(0u);
if (bytes.size() > 256u) bytes.resize(256u);
+67 -2
View File
@@ -7,6 +7,7 @@
#include <cstring>
#include <iostream>
#include <sstream>
#include <system_error>
#include <unordered_map>
#include <vector>
@@ -492,9 +493,27 @@ void Runtime::register_hle(std::string library, std::uint32_t nid, HleFunction f
bool Runtime::has_function(std::uint32_t address) const { return lookup_function(address) != nullptr; }
std::size_t Runtime::function_count() const noexcept { return functions_.size(); }
void Runtime::set_game_root(std::filesystem::path root) { game_root_ = std::filesystem::weakly_canonical(std::move(root)); }
void Runtime::set_game_root(std::filesystem::path root) {
game_root_ = std::filesystem::weakly_canonical(std::move(root));
folded_paths_.clear();
}
const std::filesystem::path &Runtime::game_root() const noexcept { return game_root_; }
namespace {
bool ascii_iequal(std::string_view left, std::string_view right) {
if (left.size() != right.size()) return false;
const auto fold = [](unsigned char value) {
return value >= 'A' && value <= 'Z' ? static_cast<unsigned char>(value - 'A' + 'a') : value;
};
for (std::size_t index = 0; index < left.size(); ++index) {
if (fold(static_cast<unsigned char>(left[index])) !=
fold(static_cast<unsigned char>(right[index])))
return false;
}
return true;
}
}
std::filesystem::path Runtime::translate_path(const std::string &psp_path) const {
std::string relative = psp_path;
const auto colon = relative.find(':');
@@ -506,7 +525,53 @@ std::filesystem::path Runtime::translate_path(const std::string &psp_path) const
if (part == "..") throw Error("Rejected PSP path traversal: " + psp_path);
if (part != ".") clean /= part;
}
return game_root_ / clean;
const std::string key = clean.generic_string();
if (const auto cached = folded_paths_.find(key); cached != folded_paths_.end())
return cached->second;
std::error_code error;
const std::filesystem::path direct = game_root_ / clean;
if (std::filesystem::exists(direct, error) && !error) return direct;
// Disc paths are case-insensitive.
std::filesystem::path resolved = game_root_;
bool folded = false;
bool missing = false;
for (const auto &part : clean) {
if (missing) {
resolved /= part;
continue;
}
error.clear();
const std::filesystem::path exact = resolved / part;
if (std::filesystem::exists(exact, error) && !error) {
resolved = exact;
continue;
}
error.clear();
if (!std::filesystem::is_directory(resolved, error) || error) {
missing = true;
resolved /= part;
continue;
}
bool matched = false;
error.clear();
for (std::filesystem::directory_iterator it(resolved, error), end;
!error && it != end; it.increment(error)) {
const std::filesystem::path name = it->path().filename();
if (!ascii_iequal(name.string(), part.string())) continue;
resolved = it->path();
matched = true;
if (name != part) folded = true;
break;
}
if (!matched) {
missing = true;
resolved /= part;
}
}
if (folded) folded_paths_.emplace(key, resolved);
return resolved;
}
void Runtime::run(std::uint32_t entry, std::uint64_t max_dispatches) {
+34
View File
@@ -1767,6 +1767,40 @@ int main() {
try { (void)runtime.translate_path("disc0:/../secret"); } catch (...) { traversal_rejected = true; }
require(traversal_rejected, "Path traversal was not rejected");
const auto case_root = std::filesystem::temp_directory_path() / "psprecomp_case_test";
std::filesystem::remove_all(case_root);
const auto movie = case_root / "psp_game" / "usrdir" / "movies";
std::filesystem::create_directories(movie);
{
std::ofstream out(movie / "logo.pmf", std::ios::binary);
out << "pmf";
}
runtime.set_game_root(case_root);
const auto folded = runtime.translate_path("disc0:/PSP_GAME/USRDIR/MOVIES/LOGO.PMF");
require(std::filesystem::equivalent(folded, movie / "logo.pmf"),
"Case-insensitive disc path failed");
const auto folded_again = runtime.translate_path("disc0:/PSP_GAME/USRDIR/MOVIES/LOGO.PMF");
require(folded_again == folded, "Folded disc path was not stable");
const auto missing = runtime.translate_path("disc0:/PSP_GAME/USRDIR/MOVIES/NO_SUCH.BIN");
require(missing.filename() == "NO_SUCH.BIN", "Missing disc file kept the guest name");
require(std::filesystem::equivalent(missing.parent_path(), movie),
"Missing disc file lost the folded parent");
std::filesystem::remove_all(case_root);
const auto exact_root = std::filesystem::temp_directory_path() / "psprecomp_case_exact";
std::filesystem::remove_all(exact_root);
const auto exact_file = exact_root / "PSP_GAME" / "USRDIR" / "data.bin";
std::filesystem::create_directories(exact_file.parent_path());
{
std::ofstream out(exact_file, std::ios::binary);
out << "x";
}
runtime.set_game_root(exact_root);
const auto exact = runtime.translate_path("disc0:/PSP_GAME/USRDIR/data.bin");
require(std::filesystem::equivalent(exact, exact_file), "Exact disc path failed");
require(exact.filename() == "data.bin", "Exact disc path changed case");
std::filesystem::remove_all(exact_root);
const auto temp = std::filesystem::temp_directory_path() / "psprecomp_sha_test.txt";
{ std::ofstream out(temp, std::ios::binary); out << "abc"; }
require(psprecomp::sha256_file(temp) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",