mirror of
https://github.com/elmasas/lcs-recomp
synced 2026-10-20 23:19:46 -04:00
Handle lowercase game path
This commit is contained in:
@@ -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_{};
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user