fix(relinker): preserve inputs when writing desktop assets
This commit is contained in:
parent
a11c8f7f70
commit
1f34f6e8d4
|
|
@ -115,6 +115,19 @@ int Run(const int argc, char* argv[]) {
|
|||
const Elfpatcher::Linux::LinuxDesktopEntryWriter desktopEntryWriter;
|
||||
const auto desktopEntry = args.toWindows || args.toMacos ? std::nullopt : desktopEntryWriter.Prepare(Io::NativePath(args.inputPath).parent_path() / "sce_sys", Io::NativePath(absPath));
|
||||
|
||||
const auto checkOutputInputs = [&](const std::filesystem::path& path, const char* kind) {
|
||||
if (!std::filesystem::exists(path)) return;
|
||||
if (std::filesystem::equivalent(Io::NativePath(args.inputPath), path))
|
||||
throw Domain::RelinkerException(std::string(kind) + " output would overwrite the input executable: " + Io::Utf8Path(path));
|
||||
for (const auto& artifact : guestArtifacts)
|
||||
if (std::filesystem::equivalent(artifact.SourcePath, path))
|
||||
throw Domain::RelinkerException(std::string(kind) + " output would overwrite an input module: " + Io::Utf8Path(path));
|
||||
};
|
||||
if (desktopEntry) {
|
||||
checkOutputInputs(desktopEntry->IconPath, "Icon");
|
||||
checkOutputInputs(desktopEntry->EntryPath, "Desktop entry");
|
||||
}
|
||||
|
||||
if (args.writeRegistry) {
|
||||
const std::filesystem::path outFsPath = Io::NativePath(absPath);
|
||||
std::vector<std::filesystem::path> registryPaths{
|
||||
|
|
@ -122,14 +135,7 @@ int Run(const int argc, char* argv[]) {
|
|||
};
|
||||
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));
|
||||
}
|
||||
for (const auto& path : registryPaths) checkOutputInputs(path, "Registry");
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import sys
|
|||
import tempfile
|
||||
|
||||
from test_guest_intel_trampolines import PLAIN_SITE, guest_fixture, main_fixture
|
||||
from test_linux_desktop_entry_output import png
|
||||
|
||||
|
||||
def check_registry_inputs(relinker, work, windows):
|
||||
|
|
@ -52,10 +53,66 @@ def check_registry_inputs(relinker, work, windows):
|
|||
assert not (case / "app0").exists()
|
||||
|
||||
|
||||
def check_desktop_inputs(relinker, work):
|
||||
for desktop in (False, True):
|
||||
for bundled in (False, True):
|
||||
for alias in ("direct", "hardlink", "symlink"):
|
||||
for registry in (False, True):
|
||||
case = work / f"desktop-{desktop}-{bundled}-{alias}-{registry}"
|
||||
modules = case / "sce_module"
|
||||
modules.mkdir(parents=True)
|
||||
sce_sys = case / "sce_sys"
|
||||
sce_sys.mkdir()
|
||||
image = png(1, 1)
|
||||
(sce_sys / "icon0.png").write_bytes(image)
|
||||
output = (modules if bundled and alias == "direct" else case) / "output.elf"
|
||||
paths = [Path(f"{output}.png"), Path(f"{output}.desktop")]
|
||||
target = paths[int(desktop)]
|
||||
source = target if not bundled and alias == "direct" else case / "input.elf"
|
||||
original = bytes(main_fixture())
|
||||
source.write_bytes(original)
|
||||
module = target if bundled and alias == "direct" else modules / "sample.prx"
|
||||
module_original = bytes(guest_fixture(PLAIN_SITE))
|
||||
module.write_bytes(module_original)
|
||||
protected = module if bundled else source
|
||||
if alias == "hardlink":
|
||||
os.link(protected, target)
|
||||
elif alias == "symlink":
|
||||
try:
|
||||
target.symlink_to(protected)
|
||||
except OSError as error:
|
||||
if os.name != "nt" or error.winerror != 1314:
|
||||
raise
|
||||
print("Desktop symbolic-link case skipped: Windows symlink privilege unavailable")
|
||||
continue
|
||||
previous_output = b"previous executable"
|
||||
output.write_bytes(previous_output)
|
||||
untouched = paths[int(not desktop)]
|
||||
previous_auxiliary = b"previous auxiliary output"
|
||||
untouched.write_bytes(previous_auxiliary)
|
||||
registry_path = output.with_suffix(".registry.json")
|
||||
previous_registry = b"previous registry"
|
||||
registry_path.write_bytes(previous_registry)
|
||||
options = ["--registry"] if registry else []
|
||||
result = subprocess.run([str(relinker), *options, str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
kind = "Desktop entry" if desktop else "Icon"
|
||||
owner = "an input module" if bundled else "the input executable"
|
||||
assert result.returncode == 2 and f"{kind} output would overwrite {owner}" in result.stderr, (result.stdout, result.stderr)
|
||||
assert str(target) in result.stderr, result.stderr
|
||||
assert source.read_bytes() == original and module.read_bytes() == module_original
|
||||
assert output.read_bytes() == previous_output and untouched.read_bytes() == previous_auxiliary
|
||||
assert registry_path.read_bytes() == previous_registry
|
||||
assert target.read_bytes() == (module_original if bundled else original)
|
||||
assert (sce_sys / "icon0.png").read_bytes() == image
|
||||
assert not (output.parent / "app0").exists()
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-input-preservation-") as directory:
|
||||
work = Path(directory)
|
||||
check_desktop_inputs(relinker, work)
|
||||
for windows in (False, True):
|
||||
options = ["--windows"] if windows else []
|
||||
check_registry_inputs(relinker, work, windows)
|
||||
|
|
@ -115,6 +172,13 @@ def main():
|
|||
module_original = guest_fixture(PLAIN_SITE)
|
||||
module.write_bytes(module_original)
|
||||
output = case / ("output.exe" if windows else "output.elf")
|
||||
if not windows:
|
||||
(case / "sce_sys").mkdir()
|
||||
image = png(1, 1)
|
||||
(case / "sce_sys" / "icon0.png").write_bytes(image)
|
||||
if existing:
|
||||
Path(f"{output}.png").write_bytes(b"previous icon")
|
||||
Path(f"{output}.desktop").write_bytes(b"previous desktop entry")
|
||||
if existing:
|
||||
output.write_bytes(b"previous output")
|
||||
result = subprocess.run([str(relinker), *options, "--registry", str(source), str(output)],
|
||||
|
|
@ -126,6 +190,9 @@ def main():
|
|||
or not output.with_suffix(".registry.json").exists()
|
||||
or source.read_bytes() != original or module.read_bytes() != module_original):
|
||||
raise AssertionError((windows, existing, result.returncode, result.stdout, result.stderr))
|
||||
if not windows:
|
||||
assert Path(f"{output}.png").read_bytes() == image
|
||||
assert Path(f"{output}.desktop").read_text().startswith("[Desktop Entry]\n")
|
||||
print("Input preservation tests passed")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ relinker --macos source/input.elf eboot
|
|||
|
||||
Add `--to-intel` for Intel hosts, or `--to-rosetta` for Rosetta; a title that runs under Rosetta on Apple silicon is relinked with `--macos --to-rosetta`. The output format defaults to Linux ELF regardless of the filename; `.exe` alone does not select Windows.
|
||||
|
||||
The executable output must not refer to the input executable or a bundled module being converted, including through a hard link or symbolic link. An existing output file can be replaced if it is separate from those inputs.
|
||||
The executable, registry, icon and desktop entry output files must not refer to the input executable or a bundled module being converted, including through a hard link or symbolic link. These collisions fail before any output is written. An existing output file can be replaced if it is separate from those inputs.
|
||||
|
||||
## Options
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue