fix(relinker): preserve input binaries when writing registries

This commit is contained in:
lorkydey 2026-10-11 13:38:27 +02:00
parent 0f863c1022
commit 0cfdc79e59
4 changed files with 64 additions and 7 deletions

View File

@ -117,13 +117,23 @@ int Run(const int argc, char* argv[]) {
if (args.writeRegistry) {
const std::filesystem::path outFsPath = Io::NativePath(absPath);
const std::string registryPath = Io::Utf8Path(outFsPath.parent_path() / Io::NativePath(Io::Utf8Path(outFsPath.stem()) + ".registry.json"));
const auto registryWriter = std::make_shared<Relinker::CallRegistryWriter>();
fileWriter.Write(registryPath, registryWriter->WriteCallRegistry(result.RegistryEntries));
for (const auto& artifact : guestArtifacts) {
const auto modulePath = outFsPath.parent_path() / Io::NativePath(Io::Utf8Path(outFsPath.stem()) + "." + Io::Utf8Path(artifact.Path.filename()) + ".registry.json");
fileWriter.Write(Io::Utf8Path(modulePath), registryWriter->WriteModuleImports(artifact.Imports));
std::vector<std::filesystem::path> registryPaths{
outFsPath.parent_path() / Io::NativePath(Io::Utf8Path(outFsPath.stem()) + ".registry.json")
};
for (const auto& artifact : guestArtifacts)
registryPaths.push_back(outFsPath.parent_path() / Io::NativePath(Io::Utf8Path(outFsPath.stem()) + "." + Io::Utf8Path(artifact.Path.filename()) + ".registry.json"));
for (const auto& path : registryPaths) {
if (!std::filesystem::exists(path)) continue;
if (std::filesystem::equivalent(Io::NativePath(args.inputPath), path))
throw Domain::RelinkerException("Registry output would overwrite the input executable: " + Io::Utf8Path(path));
for (const auto& artifact : guestArtifacts)
if (std::filesystem::equivalent(artifact.SourcePath, path))
throw Domain::RelinkerException("Registry output would overwrite an input module: " + Io::Utf8Path(path));
}
const auto registryWriter = std::make_shared<Relinker::CallRegistryWriter>();
fileWriter.Write(Io::Utf8Path(registryPaths.front()), registryWriter->WriteCallRegistry(result.RegistryEntries));
for (std::size_t index = 0; index < guestArtifacts.size(); ++index)
fileWriter.Write(Io::Utf8Path(registryPaths[index + 1]), registryWriter->WriteModuleImports(guestArtifacts[index].Imports));
}
auto byteWriter = std::make_shared<Io::ByteWriter>();

View File

@ -56,6 +56,7 @@ struct GuestArtifact {
std::filesystem::path Path;
std::vector<std::uint8_t> Bytes;
std::vector<Domain::CallRegistryEntry> Imports;
std::filesystem::path SourcePath;
};
class GuestModuleBuilder {

View File

@ -386,7 +386,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
}
}
dynamic.GuestModules.push_back(std::move(runtime));
artifacts.push_back({target, std::move(output), std::move(imports)});
artifacts.push_back({target, std::move(output), std::move(imports), image.SourcePath});
std::cout << " [DONE]" << std::endl;
}
return artifacts;

View File

@ -7,12 +7,58 @@ import tempfile
from test_guest_intel_trampolines import PLAIN_SITE, guest_fixture, main_fixture
def check_registry_inputs(relinker, work, windows):
options = ["--windows"] if windows else []
for module_registry in (False, True):
for bundled in (False, True):
for alias in ("direct", "hardlink", "symlink"):
if bundled and alias == "direct":
continue
case = work / f"registry-{windows}-{module_registry}-{bundled}-{alias}"
modules = case / "sce_module"
modules.mkdir(parents=True)
module = modules / "sample.prx"
module_original = bytes(guest_fixture(PLAIN_SITE))
module.write_bytes(module_original)
output = case / ("output.exe" if windows else "output.elf")
registries = [case / "output.registry.json", case / "output.sample.prx.guest.prx.registry.json"]
registry = registries[int(module_registry)]
source = registry if alias == "direct" else case / "input.elf"
original = bytes(main_fixture())
source.write_bytes(original)
protected = module if bundled else source
if alias == "hardlink":
os.link(protected, registry)
elif alias == "symlink":
try:
registry.symlink_to(protected)
except OSError as error:
if os.name != "nt" or error.winerror != 1314:
raise
print("Registry symbolic-link case skipped: Windows symlink privilege unavailable")
continue
previous_registry = b"previous registry"
untouched = registries[int(not module_registry)]
untouched.write_bytes(previous_registry)
previous_output = b"previous executable"
output.write_bytes(previous_output)
result = subprocess.run([str(relinker), *options, "--registry", str(source), str(output)],
capture_output=True, text=True, timeout=20)
expected = "Registry output would overwrite an input module" if bundled else "Registry output would overwrite the input executable"
assert result.returncode == 2 and expected in result.stderr, (result.returncode, result.stdout, result.stderr)
assert source.read_bytes() == original and module.read_bytes() == module_original
assert output.read_bytes() == previous_output and untouched.read_bytes() == previous_registry
assert registry.read_bytes() == (module_original if bundled else original)
assert not (case / "app0").exists()
def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-input-preservation-") as directory:
work = Path(directory)
for windows in (False, True):
options = ["--windows"] if windows else []
check_registry_inputs(relinker, work, windows)
for bundled in (False, True):
for alias in ("direct", "normalized", "hardlink", "symlink"):
case = work / f"{windows}-{bundled}-{alias}"