fix(relinker): skip unreadable candidates and fold case for --windows in the needed-module report

This commit is contained in:
Adnan Khan committed 2026-10-22 14:02:37 +05:30
1 parent 00d6b33126
commit f3dcf6d8be
5 files changed
+45 -13

No files matched your search

+1 -1
View File
@@ -16,6 +16,6 @@ bool SameDirectory(const std::filesystem::path& first, const std::filesystem::pa
NativeModules ReadModules(const std::filesystem::path& inputPath, const std::filesystem::path& sceModulePath, const std::set<std::string>& excludedModules, const std::vector<std::string>& moduleDirectories);
void ReportUnconvertedNeeded(const NativeModules& modules, const std::vector<std::string>& neededLibraries, std::ostream& out);
void ReportUnconvertedNeeded(const NativeModules& modules, const std::vector<std::string>& neededLibraries, bool foldCase, std::ostream& out);
}
+24 -10
View File
@@ -8,6 +8,7 @@
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <string_view>
#include <tuple>
@@ -21,12 +22,12 @@ struct CandidateHeader {
std::uint16_t Type = 0;
};
CandidateHeader ReadCandidateHeader(const std::filesystem::path& path) {
std::optional<CandidateHeader> TryReadCandidateHeader(const std::filesystem::path& path) {
std::ifstream stream(path, std::ios::binary);
if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + Io::Utf8Path(path));
if (!stream) return std::nullopt;
char header[18]{};
stream.read(header, sizeof header);
if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + Io::Utf8Path(path));
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]); };
@@ -36,6 +37,17 @@ CandidateHeader ReadCandidateHeader(const std::filesystem::path& path) {
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());
}
@@ -130,10 +142,12 @@ NativeModules ReadModules(const std::filesystem::path& inputPath, const std::fil
return result;
}
void ReportUnconvertedNeeded(const NativeModules& modules, const std::vector<std::string>& neededLibraries, std::ostream& out) {
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)
if (!Domain::ModuleSuffix(name).empty()) stems[std::string(ModuleStem(name))].push_back(name);
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;
@@ -147,13 +161,13 @@ void ReportUnconvertedNeeded(const NativeModules& modules, const std::vector<std
continue;
}
if (!entry->is_regular_file()) continue;
const auto filename = Io::Utf8Path(entry->path().filename());
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;
const auto header = ReadCandidateHeader(entry->path());
if (!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});
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) {
+1 -1
View File
@@ -76,7 +76,7 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath,
progress.IncludeFile(Io::Utf8Path(input.OutputPath));
for (const auto& module : input.Modules) progress.IncludeFile(Io::Utf8Path(module.Path));
auto converted = Relinker::Conversion(progress).Convert(std::move(input), options);
if (!args.skipSceModule) Cli::ReportUnconvertedNeeded(modules, converted.Analysis.DynamicSection.NeededLibraries, std::cout);
if (!args.skipSceModule) Cli::ReportUnconvertedNeeded(modules, converted.Analysis.DynamicSection.NeededLibraries, args.toWindows, std::cout);
return {inputPath, absPath, std::move(converted.Analysis), std::move(converted.Modules), std::move(converted.Executable), std::move(converted.WindowsDiagnostics), std::move(converted.AppliedPatches), converted.LinuxBase};
}
@@ -143,6 +143,24 @@ def check_needed_outside(relinker, work):
(case / "needed.prx").write_bytes(sce_module)
assert convert(case, ["--skip-sce-module"]) == []
case = work / "outside-case"
(case / "sce_module").mkdir(parents=True)
(case / "Media").mkdir()
(case / "Media" / "NEEDED.PRX").write_bytes(sce_module)
assert convert(case) == []
assert convert(case, ["--windows"]) == [message + "needed.prx at Media/NEEDED.PRX; add --module-dir Media"]
if os.name != "nt" and os.geteuid() != 0:
case = work / "outside-unreadable"
(case / "sce_module").mkdir(parents=True)
(case / "Media").mkdir()
(case / "Media" / "needed.prx").write_bytes(sce_module)
(case / "needed.prx").write_bytes(self_container)
(case / "needed.prx").chmod(0)
try:
assert convert(case) == [message + "needed.prx at Media/needed.prx; add --module-dir Media"]
finally:
(case / "needed.prx").chmod(0o644)
def main():
+1 -1
View File
@@ -27,7 +27,7 @@ relinker [options] <input.elf> <output>
An explicit `--module-dir` also works when the module parent has no `sce_module`, `sce_modules` or `prx` directory. For a homebrew bundle with shared libraries beside its executable, use `--module-dir .`; no empty standard directory is required. Without an explicit module directory, the standard-directory requirement remains.
A module the executable needs that is not converted is looked for under the module parent directory, outside the module directories. Each SELF container or `ET_SCE_DYNAMIC` ELF found there whose name matches, up to the `.prx`, `.sprx`, `.suprx` or `.debug_prx` suffix, is reported after the conversion with the `--module-dir` that would include it; the output still refers to a host library of that name.
A module the executable needs that is not converted is looked for under the module parent directory, outside the module directories. Each SELF container or `ET_SCE_DYNAMIC` ELF found there whose name matches, up to the `.prx`, `.sprx`, `.suprx` or `.debug_prx` suffix (ignoring case with `--windows`, as the guest module match does), is reported after the conversion with the `--module-dir` that would include it; the output still refers to a host library of that name. A file that cannot be read is not reported.
Linux output: