Merge pull request #716 from merwie/feat/relinker-needed-modules-outside-module-dirs
feat(relinker): relink needed modules outside the module directories
This commit is contained in:
commit
f71bf7fd40
|
|
@ -218,6 +218,8 @@ if(BUILD_TESTING)
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_intel_trampolines.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_tls COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_tls.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_needed_modules COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_needed_modules.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_module_directories COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_directories.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_symbol_names COMMAND ${Python3_EXECUTABLE}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ int main(const int argc, char* argv[]) {
|
|||
<< std::filesystem::path(absPath).filename().string() << "\n"
|
||||
<< "libs/\n *.prx\napp0/\n <game resources>\n";
|
||||
for (const auto& artifact : guestArtifacts)
|
||||
std::cout << " " << artifact.Path.parent_path().filename().string() << "/" << artifact.Path.filename().string() << '\n';
|
||||
std::cout << " " << artifact.Path.lexically_relative(std::filesystem::path(absPath).parent_path() / "app0").generic_string() << '\n';
|
||||
std::cout << "Game resources and system libraries must be placed in this layout separately.\n";
|
||||
if (args.runPath != "$ORIGIN/libs") std::cout << "Custom library search path (--rpath): " << args.runPath << '\n';
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
if (hasPrx) directories.push_back(prx);
|
||||
std::vector<std::filesystem::path> paths;
|
||||
std::set<std::string> unmatchedExclusions = excludedModules;
|
||||
const auto isElf = [](const std::filesystem::path& path) {
|
||||
std::ifstream stream(path, std::ios::binary);
|
||||
if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + path.string());
|
||||
char magic[4]{};
|
||||
stream.read(magic, 4);
|
||||
if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + path.string());
|
||||
return stream.gcount() == 4 && static_cast<unsigned char>(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F';
|
||||
};
|
||||
for (const auto& directory : directories) {
|
||||
if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string());
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
|
|
@ -36,14 +44,35 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
continue;
|
||||
}
|
||||
if (!entry.is_regular_file()) continue;
|
||||
std::ifstream stream(entry.path(), std::ios::binary);
|
||||
if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string());
|
||||
char magic[4]{};
|
||||
stream.read(magic, 4);
|
||||
if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string());
|
||||
if (stream.gcount() == 4 && static_cast<unsigned char>(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path());
|
||||
if (isElf(entry.path())) paths.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
if (dynamic.DynamicSegmentData.size() % 16 != 0) throw Domain::RelinkerException("Invalid executable dependency table");
|
||||
std::set<std::string> missingNeeded;
|
||||
for (std::size_t offset = 0; offset < dynamic.DynamicSegmentData.size(); offset += 16) {
|
||||
if (Io::ReadU64(dynamic.DynamicSegmentData, offset) != 1) continue;
|
||||
const auto nameOffset = Io::ReadU64(dynamic.DynamicSegmentData, offset + 8);
|
||||
if (nameOffset >= dynamic.DynStrData.size()) throw Domain::RelinkerException("Invalid dependency string offset");
|
||||
const auto start = dynamic.DynStrData.begin() + static_cast<std::ptrdiff_t>(nameOffset);
|
||||
const auto end = std::find(start, dynamic.DynStrData.end(), 0);
|
||||
if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated dependency string");
|
||||
const std::string name(start, end);
|
||||
if (excludedModules.contains(name)) continue;
|
||||
if (std::none_of(paths.begin(), paths.end(), [&](const auto& path) { return path.filename().string() == name; })) missingNeeded.insert(name);
|
||||
}
|
||||
if (!missingNeeded.empty()) {
|
||||
std::map<std::string, std::filesystem::path> found;
|
||||
for (auto it = std::filesystem::recursive_directory_iterator(root); it != std::filesystem::recursive_directory_iterator(); ++it) {
|
||||
if (it->is_directory() && std::find(directories.begin(), directories.end(), it->path()) != directories.end()) {
|
||||
it.disable_recursion_pending();
|
||||
continue;
|
||||
}
|
||||
const auto name = it->path().filename().string();
|
||||
if (!it->is_regular_file() || !missingNeeded.contains(name) || !isElf(it->path())) continue;
|
||||
if (!found.emplace(name, it->path()).second) throw Domain::RelinkerException("Ambiguous needed module: " + found.at(name).string() + " and " + it->path().string());
|
||||
}
|
||||
for (const auto& [name, path] : found) paths.push_back(path);
|
||||
}
|
||||
if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded guest module file not found: " + *unmatchedExclusions.begin());
|
||||
std::sort(paths.begin(), paths.end());
|
||||
if (paths.empty()) return {};
|
||||
|
|
@ -193,7 +222,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size());
|
||||
Io::AppendString(dynamic.DynStrData, name);
|
||||
};
|
||||
for (const auto index : order) if (!windows) addNeeded("$ORIGIN/app0/" + images[index].SourcePath.parent_path().filename().generic_string() + "/" + images[index].OutputName);
|
||||
for (const auto index : order) if (!windows) addNeeded("$ORIGIN/app0/" + images[index].SourcePath.parent_path().lexically_relative(root).generic_string() + "/" + images[index].OutputName);
|
||||
for (const auto& name : hostLibraries) addNeeded(name);
|
||||
std::string guestRunPath = runPath;
|
||||
if (!windows) {
|
||||
|
|
@ -204,7 +233,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
std::vector<GuestArtifact> artifacts;
|
||||
for (const auto index : order) {
|
||||
const auto& image = images[index];
|
||||
const auto relativeDirectory = "app0/" + image.SourcePath.parent_path().filename().generic_string();
|
||||
const auto relativeDirectory = "app0/" + image.SourcePath.parent_path().lexically_relative(root).generic_string();
|
||||
const auto destination = std::filesystem::absolute(outputPath).parent_path() / relativeDirectory;
|
||||
const auto target = destination / image.OutputName;
|
||||
if (target.lexically_normal() == std::filesystem::absolute(outputPath).lexically_normal()) throw Domain::RelinkerException("Guest output collides with the executable output");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_guest_intel_trampolines import main_fixture
|
||||
from test_guest_module_directories import module_with_symbol, needed_libraries
|
||||
|
||||
|
||||
NEEDED = b"needed.prx"
|
||||
|
||||
|
||||
def executable_with_needed():
|
||||
image = main_fixture()
|
||||
strings = b"\0" + NEEDED + b"\0"
|
||||
image[0x4800:0x4800 + len(strings)] = strings
|
||||
tags = []
|
||||
for position in range(0x4600, 0x4600 + 0x200, 16):
|
||||
tag, value = struct.unpack_from("<qQ", image, position)
|
||||
if tag == 0:
|
||||
break
|
||||
tags.append((tag, value))
|
||||
tags = [(5, 0x4800) if tag == 5 else (10, len(strings)) if tag == 10 else (tag, value) for tag, value in tags]
|
||||
tags += [(1, 1), (0, 0)]
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into("<qQ", image, 0x4600 + index * 16, *tag)
|
||||
struct.pack_into("<QQ", image, 120 + 32, len(tags) * 16, len(tags) * 16)
|
||||
return image
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-needed-modules-") as directory:
|
||||
work = Path(directory)
|
||||
|
||||
def convert(case, windows):
|
||||
(case / "sce_module").mkdir(parents=True, exist_ok=True)
|
||||
source = case / "input.elf"
|
||||
source.write_bytes(executable_with_needed())
|
||||
output = case / ("output.exe" if windows else "output.elf")
|
||||
result = subprocess.run([str(relinker), *(["--windows"] if windows else []), str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
return result, output
|
||||
|
||||
for windows in (False, True):
|
||||
case = work / f"{windows}-found"
|
||||
modules = case / "Media" / "Modules"
|
||||
modules.mkdir(parents=True)
|
||||
(modules / "needed.prx").write_bytes(module_with_symbol(True))
|
||||
(case / "Media" / "needed.prx").write_text("not ELF")
|
||||
result, output = convert(case, windows)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
artifact = case / "app0" / "Media" / "Modules" / "needed.prx.guest.prx"
|
||||
assert artifact.read_bytes().startswith(b"MZ" if windows else b"\x7fELF"), artifact
|
||||
assert list((case / "app0").rglob("*.guest.prx")) == [artifact]
|
||||
assert " Media/Modules/needed.prx.guest.prx\n" in result.stdout, result.stdout
|
||||
if windows and os.name == "nt":
|
||||
run = subprocess.run([str(output)], capture_output=True, text=True, timeout=30)
|
||||
assert run.returncode == 42, (run.returncode, run.stdout, run.stderr)
|
||||
if not windows:
|
||||
needed = needed_libraries(output.read_bytes())
|
||||
assert needed == ["$ORIGIN/app0/Media/Modules/needed.prx.guest.prx"], needed
|
||||
|
||||
case = work / f"{windows}-ambiguous"
|
||||
for name in ("first", "second"):
|
||||
(case / name).mkdir(parents=True)
|
||||
(case / name / "needed.prx").write_bytes(module_with_symbol(True))
|
||||
result, output = convert(case, windows)
|
||||
assert result.returncode == 2 and "Ambiguous needed module" in result.stderr, result.stderr
|
||||
assert not output.exists(), output
|
||||
print("Guest needed module tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue