Merge pull request #108 from boykopovar/refactor/relinker-friendly-usage
fix(relinker): use app0 guest module layout and clarify output diagnostic
This commit is contained in:
commit
1ff73445b1
|
|
@ -12,6 +12,8 @@ Includes a [relinker](core/relinker) that converts executable to the target syst
|
|||
|
||||
[](https://boykopovar.github.io/AnyPS5/)
|
||||
|
||||
<sub>* System libraries: percentage of the functions known to the project so far (declared in [core/libs/prx](core/libs/prx)), not of every PS5 system function. The total grows as more functions are declared.</sub>
|
||||
|
||||
[List of verified games](docs/user/COMPATIBILITY.md)
|
||||
|
||||
Dreaming Sarah (2D platformer) runs at a stable 60 fps on a GTX 1050 Ti / i5-7500 3.4GHz.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ int main(const int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
try {
|
||||
auto extension = std::filesystem::path(args.outputPath).extension().string();
|
||||
for (auto& character : extension) if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
|
||||
if (!args.toWindows && extension == ".exe") std::cerr << "WARNING: Output filename ends with .exe, but --windows was not specified. The output will be a Linux ELF executable.\n";
|
||||
Io::FileReader fileReader;
|
||||
Io::FileWriter fileWriter;
|
||||
|
||||
|
|
@ -111,6 +114,15 @@ int main(const int argc, char* argv[]) {
|
|||
}
|
||||
fileWriter.Write(absPath, executableBytes);
|
||||
std::cout << "External prx references: " << result.RegistryEntries.size() << "\nOutput file: " << absPath << '\n';
|
||||
std::cout << "Expected runtime layout (relative to the output executable):\n"
|
||||
<< std::filesystem::path(absPath).filename().string() << "\n"
|
||||
<< "libs/\n *.prx\napp0/\n <game resources>\n";
|
||||
if (!guestArtifacts.empty()) {
|
||||
std::cout << " " << guestArtifacts.front().Path.parent_path().filename().string() << "/\n";
|
||||
for (const auto& artifact : guestArtifacts) std::cout << " " << artifact.Path.filename().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';
|
||||
|
||||
if (args.autorun) return Cli::Autorun(absPath, args.toWindows);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
const bool hasSingular = std::filesystem::exists(singular);
|
||||
const bool hasPlural = std::filesystem::exists(plural);
|
||||
if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist beside the input executable");
|
||||
if (!hasSingular && !hasPlural) {
|
||||
std::cout << "WARNING: sce_module not found beside the input executable; it may be unnecessary or missing.\n";
|
||||
return {};
|
||||
}
|
||||
if (!hasSingular && !hasPlural) throw Domain::RelinkerException("sce_module/sce_modules was not found beside the input executable: " + root.string() + ". Use --skip-sce-module only if this game can run without these modules.");
|
||||
const auto directory = hasSingular ? singular : plural;
|
||||
if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string());
|
||||
std::vector<std::filesystem::path> paths;
|
||||
|
|
@ -119,18 +116,17 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size());
|
||||
Io::AppendString(dynamic.DynStrData, name);
|
||||
};
|
||||
const auto relativeDirectory = directory.filename().generic_string();
|
||||
const auto relativeDirectory = "app0/" + directory.filename().generic_string();
|
||||
for (const auto index : order) if (!windows) addNeeded("$ORIGIN/" + relativeDirectory + "/" + images[index].OutputName);
|
||||
for (const auto& name : hostLibraries) addNeeded(name);
|
||||
std::string guestRunPath = runPath;
|
||||
if (!windows) {
|
||||
if (guestRunPath == "$ORIGIN") guestRunPath = "$ORIGIN/..";
|
||||
else if (guestRunPath.starts_with("$ORIGIN/")) guestRunPath.insert(8, "../");
|
||||
if (guestRunPath == "$ORIGIN") guestRunPath = "$ORIGIN/../..";
|
||||
else if (guestRunPath.starts_with("$ORIGIN/")) guestRunPath.insert(8, "../../");
|
||||
else if (!std::filesystem::path(guestRunPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN");
|
||||
}
|
||||
std::vector<GuestArtifact> artifacts;
|
||||
const auto destination = std::filesystem::absolute(outputPath).parent_path() / directory.filename();
|
||||
if (std::filesystem::exists(destination) && std::filesystem::equivalent(destination, directory)) throw Domain::RelinkerException("Guest output directory must differ from the source module directory");
|
||||
const auto destination = std::filesystem::absolute(outputPath).parent_path() / relativeDirectory;
|
||||
for (const auto index : order) {
|
||||
const auto& image = images[index];
|
||||
const auto target = destination / image.OutputName;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ def main():
|
|||
if access:
|
||||
data[0x200:0x20a] = bytes.fromhex("64 48 8b 04 25 00 00 00 00 c3")
|
||||
source.write_bytes(data)
|
||||
result = subprocess.run([str(relinker), "--windows", str(source), str(output)],
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
if error:
|
||||
assert result.returncode == 2 and error in result.stderr and not output.exists(), result
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ def main():
|
|||
source = Path(directory) / "input.elf"
|
||||
output = Path(directory) / "output.elf"
|
||||
source.write_bytes(fixture())
|
||||
result = subprocess.run([str(relinker), str(source), str(output)], capture_output=True, text=True, timeout=20)
|
||||
missing = subprocess.run([str(relinker), str(source), str(output)], capture_output=True, text=True, timeout=20)
|
||||
assert missing.returncode != 0 and not output.exists(), missing
|
||||
assert "sce_module/sce_modules was not found" in missing.stderr, missing.stderr
|
||||
assert "--skip-sce-module only if this game" in missing.stderr, missing.stderr
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(output)], capture_output=True, text=True, timeout=20)
|
||||
if result.returncode != 0:
|
||||
raise AssertionError((result.returncode, result.stdout, result.stderr))
|
||||
segments = loads(output.read_bytes())
|
||||
|
|
@ -49,6 +53,12 @@ def main():
|
|||
alignment = max(segment[7] for segment in segments)
|
||||
if alignment != 0x4000 or first[3] % alignment != 0 or first[7] != alignment:
|
||||
raise AssertionError(("first PT_LOAD is not aligned to the largest segment alignment", segments))
|
||||
assert "output.elf\nlibs/\n *.prx\napp0/" in result.stdout, result.stdout
|
||||
named_exe = Path(directory) / "linux.EXE"
|
||||
warning = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(named_exe)], capture_output=True, text=True, timeout=20)
|
||||
assert warning.returncode == 0, (warning.stdout, warning.stderr)
|
||||
assert "--windows was not specified" in warning.stderr, warning.stderr
|
||||
assert named_exe.read_bytes().startswith(b"\x7fELF"), "The filename must not select the output format"
|
||||
print("Linux load alignment test passed")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ def main():
|
|||
source = work / (name + ".elf")
|
||||
output = work / (name + ".exe")
|
||||
source.write_bytes(fixture(tags))
|
||||
result = subprocess.run([str(relinker), "--windows", str(source), str(output)],
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
if error is not None:
|
||||
if result.returncode != 2 or error not in result.stderr or output.exists():
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ def main():
|
|||
source = work / (name + ".elf")
|
||||
output = source.with_suffix(".exe")
|
||||
source.write_bytes(image)
|
||||
result = subprocess.run([str(relinker), "--windows", str(source), str(output)],
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
if error is not None:
|
||||
assert result.returncode == 2 and error in result.stderr and not output.exists(), result
|
||||
|
|
|
|||
|
|
@ -22,19 +22,19 @@ def main():
|
|||
source.write_bytes(fixture())
|
||||
|
||||
cui_output = work / "cui.exe"
|
||||
result = subprocess.run([str(relinker), "--windows", str(source), str(cui_output)],
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(cui_output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
assert subsystem(cui_output.read_bytes()) == 3, "expected IMAGE_SUBSYSTEM_WINDOWS_CUI by default"
|
||||
|
||||
gui_output = work / "gui.exe"
|
||||
result = subprocess.run([str(relinker), "--windows", "--windows-gui", str(source), str(gui_output)],
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", "--windows-gui", str(source), str(gui_output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
assert subsystem(gui_output.read_bytes()) == 2, "expected IMAGE_SUBSYSTEM_WINDOWS_GUI with --windows-gui"
|
||||
|
||||
bad_output = work / "bad.exe"
|
||||
result = subprocess.run([str(relinker), "--windows-gui", str(source), str(bad_output)],
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows-gui", str(source), str(bad_output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
assert result.returncode != 0 and not bad_output.exists(), result
|
||||
print("Windows GUI subsystem integration tests passed")
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ def package(platform, build, output, version):
|
|||
if not file.is_file() or file.stat().st_size == 0:
|
||||
raise RuntimeError(f"Missing or empty release file: {file}")
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(output / f"prx-{platform}-{version}.zip", "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
with zipfile.ZipFile(output / f"prx-{platform}-{version}.zip", "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
|
||||
for file in files:
|
||||
archive.write(file, arcname=file.name)
|
||||
with tarfile.open(output / f"prx-{platform}-{version}.tar.gz", "w:gz") as archive:
|
||||
archive.write(file, arcname=f"libs/{file.name}")
|
||||
with tarfile.open(output / f"prx-{platform}-{version}.tar.gz", "w:gz", compresslevel=9) as archive:
|
||||
for file in files:
|
||||
archive.add(file, arcname=file.name)
|
||||
archive.add(file, arcname=f"libs/{file.name}")
|
||||
asset = f"relinker-{version}.exe" if platform == "windows" else f"relinker-{version}"
|
||||
shutil.copy2(binary, output / asset)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue