mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-07 13:39:55 +00:00
The command-line relinker skips an ELF module whose file name extends another module's in the same directory when both have the same SONAME, a dumper's backup such as libX.prx.esbak (#7380). That check lives in the CLI's ReadModules, so the online converter, which hands every ELF in sce_module/ straight to Conversion, still stops on such a layout with "Ambiguous guest dependency name: libX.prx". The check moves into the relinker core as RemoveBackupCopies, which Conversion::Convert now runs before patching or converting modules and reports through the progress messages, so the browser prints the same "Skipping backup copy" lines. ReadModules uses the same function, so the command line and --verify-patches behave as before. Co-Authored-By: Claude Opus 5.5 <[email protected]>
170 lines
9.1 KiB
C++
170 lines
9.1 KiB
C++
#include <NativeModules.hpp>
|
|
#include <io/FileReader.hpp>
|
|
#include <io/NativePath.hpp>
|
|
#include <io/FileIdentity.hpp>
|
|
#include <domain/ImportModule.hpp>
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <string_view>
|
|
#include <system_error>
|
|
#include <tuple>
|
|
|
|
namespace Cli {
|
|
|
|
namespace {
|
|
|
|
struct CandidateHeader {
|
|
bool Elf = false;
|
|
bool Self = false;
|
|
std::uint16_t Type = 0;
|
|
};
|
|
|
|
std::optional<CandidateHeader> TryReadCandidateHeader(const std::filesystem::path& path) {
|
|
std::ifstream stream(path, std::ios::binary);
|
|
if (!stream) return std::nullopt;
|
|
char header[18]{};
|
|
stream.read(header, sizeof header);
|
|
if (stream.bad()) return std::nullopt;
|
|
CandidateHeader result;
|
|
if (stream.gcount() < 4) return result;
|
|
const auto byte = [&](const std::size_t index) { return static_cast<unsigned char>(header[index]); };
|
|
result.Self = (byte(0) == 0x4f && byte(1) == 0x15 && byte(2) == 0x3d && byte(3) == 0x1d) || (byte(0) == 0x54 && byte(1) == 0x14 && byte(2) == 0xf5 && byte(3) == 0xee);
|
|
result.Elf = byte(0) == 0x7f && header[1] == 'E' && header[2] == 'L' && header[3] == 'F';
|
|
if (result.Elf && stream.gcount() == sizeof header) result.Type = static_cast<std::uint16_t>(byte(16) | (byte(17) << 8));
|
|
return result;
|
|
}
|
|
|
|
CandidateHeader ReadCandidateHeader(const std::filesystem::path& path) {
|
|
const auto header = TryReadCandidateHeader(path);
|
|
if (!header) throw Domain::RelinkerException("Cannot read guest candidate: " + Io::Utf8Path(path));
|
|
return *header;
|
|
}
|
|
|
|
std::string FoldedName(std::string name, const bool foldCase) {
|
|
if (foldCase) for (auto& character : name) if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
|
|
return name;
|
|
}
|
|
|
|
std::string_view ModuleStem(const std::string_view name) {
|
|
return name.substr(0, name.size() - Domain::ModuleSuffix(name).size());
|
|
}
|
|
|
|
}
|
|
|
|
bool SameDirectory(const std::filesystem::path& first, const std::filesystem::path& second) {
|
|
if (std::filesystem::exists(first) && std::filesystem::exists(second)) return std::filesystem::equivalent(first, second);
|
|
return std::filesystem::weakly_canonical(first) == std::filesystem::weakly_canonical(second);
|
|
}
|
|
|
|
NativeModules ReadModules(const std::filesystem::path& inputPath, const std::filesystem::path& sceModulePath, const std::set<std::string>& excludedModules, const std::vector<std::string>& moduleDirectories) {
|
|
using namespace Relinker;
|
|
const auto root = std::filesystem::absolute(sceModulePath).lexically_normal();
|
|
if (!std::filesystem::exists(root)) throw Domain::RelinkerException("Guest module parent directory does not exist: " + Io::Utf8Path(root));
|
|
if (!std::filesystem::is_directory(root)) throw Domain::RelinkerException("Guest module parent path is not a directory: " + Io::Utf8Path(root));
|
|
const auto missingModuleMessage = std::string(GuestModulePattern) + " was not found in: " + Io::Utf8Path(root) + ". Use --skip-sce-module to disable guest module processing.";
|
|
const auto singular = root / "sce_module";
|
|
const auto plural = root / "sce_modules";
|
|
const auto prx = root / "prx";
|
|
const bool hasSingular = std::filesystem::exists(singular);
|
|
const bool hasPlural = std::filesystem::exists(plural);
|
|
const bool hasPrx = std::filesystem::exists(prx);
|
|
if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist in the guest module parent directory");
|
|
if (!hasSingular && !hasPlural && !hasPrx && moduleDirectories.empty()) throw Domain::RelinkerException(missingModuleMessage);
|
|
std::vector<std::filesystem::path> directories;
|
|
if (hasSingular || hasPlural) directories.push_back(hasSingular ? singular : plural);
|
|
if (hasPrx) directories.push_back(prx);
|
|
std::vector<std::filesystem::path> runtimeDirectories;
|
|
for (const auto& name : moduleDirectories) {
|
|
const auto relative = Io::NativePath(name).lexically_normal();
|
|
if (relative.empty() || relative.has_root_path() || *relative.begin() == "..")
|
|
throw Domain::RelinkerException("--module-dir must be a directory inside the guest module parent directory: " + name);
|
|
auto directory = (root / relative).lexically_normal();
|
|
if (!directory.has_filename()) directory = directory.parent_path();
|
|
if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + Io::Utf8Path(directory));
|
|
if (std::any_of(directories.begin(), directories.end(), [&](const auto& existing) { return SameDirectory(existing, directory); })) throw Domain::RelinkerException("Duplicate guest module directory: " + Io::Utf8Path(directory));
|
|
directories.push_back(directory);
|
|
runtimeDirectories.push_back(directory);
|
|
}
|
|
std::vector<std::filesystem::path> paths;
|
|
std::set<std::string> unmatchedExclusions = excludedModules;
|
|
for (const auto& directory : directories) {
|
|
if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + Io::Utf8Path(directory));
|
|
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
|
if (Io::Utf8Path(entry.path().filename()).ends_with(GuestModuleSuffix)) continue;
|
|
if (excludedModules.contains(Io::Utf8Path(entry.path().filename()))) {
|
|
unmatchedExclusions.erase(Io::Utf8Path(entry.path().filename()));
|
|
continue;
|
|
}
|
|
if (!entry.is_regular_file()) continue;
|
|
if (Io::SameFile(entry.path(), inputPath)) continue;
|
|
const auto header = ReadCandidateHeader(entry.path());
|
|
if (header.Elf) paths.push_back(entry.path());
|
|
else if (header.Self)
|
|
std::cout << "Skipping SELF container: " << entry.path().lexically_relative(root).generic_string()
|
|
<< " (unwrap it before relinking)\n";
|
|
}
|
|
}
|
|
if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded guest module file not found: " + *unmatchedExclusions.begin());
|
|
std::sort(paths.begin(), paths.end());
|
|
NativeModules result;
|
|
result.Root = root;
|
|
result.Directories = std::move(directories);
|
|
result.RuntimeDirectories = std::move(runtimeDirectories);
|
|
for (const auto& path : paths) result.Modules.push_back({path, Io::FileReader().Read(Io::Utf8Path(path))});
|
|
for (const auto& [copy, kept] : RemoveBackupCopies(result.Modules))
|
|
std::cout << "Skipping backup copy: " << copy.lexically_relative(root).generic_string() << " (same SONAME as "
|
|
<< kept.lexically_relative(root).generic_string() << ")\n";
|
|
return result;
|
|
}
|
|
|
|
void ReportUnconvertedNeeded(const NativeModules& modules, const std::vector<std::string>& neededLibraries, const bool foldCase, std::ostream& out) {
|
|
std::map<std::string, std::vector<std::string>> stems;
|
|
for (const auto& name : neededLibraries) {
|
|
const auto folded = FoldedName(name, foldCase);
|
|
if (!Domain::ModuleSuffix(folded).empty()) stems[std::string(ModuleStem(folded))].push_back(name);
|
|
}
|
|
if (stems.empty()) return;
|
|
struct Found {
|
|
std::filesystem::path Relative;
|
|
std::string Name;
|
|
bool Self;
|
|
};
|
|
std::vector<Found> found;
|
|
std::error_code error;
|
|
for (auto entry = std::filesystem::recursive_directory_iterator(modules.Root, std::filesystem::directory_options::skip_permission_denied, error); !error && entry != std::filesystem::recursive_directory_iterator(); entry.increment(error)) {
|
|
const bool directory = entry->is_directory(error);
|
|
if (error) {
|
|
error.clear();
|
|
continue;
|
|
}
|
|
if (directory) {
|
|
if (std::any_of(modules.Directories.begin(), modules.Directories.end(), [&](const auto& scanned) { return std::filesystem::equivalent(scanned, entry->path(), error) && !error; })) entry.disable_recursion_pending();
|
|
error.clear();
|
|
continue;
|
|
}
|
|
const auto filename = FoldedName(Io::Utf8Path(entry->path().filename()), foldCase);
|
|
if (Domain::ModuleSuffix(filename).empty()) continue;
|
|
const auto stem = stems.find(std::string(ModuleStem(filename)));
|
|
if (stem == stems.end()) continue;
|
|
if (!entry->is_regular_file(error) || error) {
|
|
error.clear();
|
|
continue;
|
|
}
|
|
const auto header = TryReadCandidateHeader(entry->path());
|
|
if (!header || (!header->Self && !(header->Elf && header->Type == 0xfe18))) continue;
|
|
for (const auto& name : stem->second) found.push_back({entry->path().lexically_relative(modules.Root), name, header->Self});
|
|
}
|
|
std::sort(found.begin(), found.end(), [](const auto& left, const auto& right) { return std::tie(left.Relative, left.Name) < std::tie(right.Relative, right.Name); });
|
|
for (const auto& [relative, name, self] : found) {
|
|
const auto directory = relative.parent_path().generic_string();
|
|
out << "Needed module not converted: " << name << " at " << relative.generic_string() << "; add --module-dir " << (directory.empty() ? "." : directory)
|
|
<< (self ? " and unwrap the SELF container" : "") << '\n';
|
|
}
|
|
}
|
|
|
|
}
|