mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #8162 from savvaniss/fix/windows-runtime-module-start
fix(relinker): start Windows runtime modules the executable needs when the title loads them
This commit is contained in:
6 files changed
+65
-8
No files matched your search
@@ -271,9 +271,13 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
||||
const auto isRuntime = [&](const std::size_t index) {
|
||||
return std::find(runtimeDirectories.begin(), runtimeDirectories.end(), images[index].SourcePath.parent_path().lexically_normal()) != runtimeDirectories.end();
|
||||
};
|
||||
const auto standardDirectory = [&](const std::size_t index) {
|
||||
const auto directory = images[index].SourcePath.parent_path().lexically_normal();
|
||||
return directory == (root / "sce_module").lexically_normal() || directory == (root / "sce_modules").lexically_normal() || directory == (root / "prx").lexically_normal();
|
||||
};
|
||||
for (const auto& name : neededNames) {
|
||||
const auto found = findGuest(name);
|
||||
if (found != guestNames.end()) require(found->second);
|
||||
if (found != guestNames.end() && (!windows || standardDirectory(found->second))) require(found->second);
|
||||
}
|
||||
for (const auto provider : executableProviders) require(provider);
|
||||
for (std::size_t index = 0; index < images.size(); ++index) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "TestProgress.hpp"
|
||||
#include <relinker/pipeline/Conversion.hpp>
|
||||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <relinker/analysis/SyscallScanner.hpp>
|
||||
#include <relinker/analysis/CodeInstructionCollector.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
@@ -10,6 +11,7 @@
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -23,7 +25,7 @@ void reject(TAction action) {
|
||||
throw std::runtime_error("Invalid conversion was accepted");
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> fixture() {
|
||||
std::vector<std::uint8_t> fixture(const std::string& needed = {}) {
|
||||
std::vector<std::uint8_t> bytes(0x3000);
|
||||
const std::array<std::uint8_t, 7> magic{0x7f, 'E', 'L', 'F', 2, 1, 1};
|
||||
std::copy(magic.begin(), magic.end(), bytes.begin());
|
||||
@@ -51,7 +53,12 @@ std::vector<std::uint8_t> fixture() {
|
||||
header(1, 1, 6, 0x2000, 0x1000);
|
||||
for (std::size_t index = 3; index < 8; ++index) header(index, 0x6fffff01, 0, 0, 0);
|
||||
bytes[0x1000] = 0xc3;
|
||||
const std::array<std::pair<std::uint64_t, std::uint64_t>, 9> tags{{{5, 0x2600}, {10, 1}, {6, 0x2620}, {11, 24}, {7, 0x2700}, {8, 0}, {9, 24}, {4, 0x2680}, {0, 0}}};
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> tags{{5, 0x2600}, {10, needed.empty() ? 1 : needed.size() + 2}, {6, 0x2620}, {11, 24}, {7, 0x2700}, {8, 0}, {9, 24}, {4, 0x2680}};
|
||||
if (!needed.empty()) {
|
||||
std::copy(needed.begin(), needed.end(), bytes.begin() + 0x2601);
|
||||
tags.push_back({1, 1});
|
||||
}
|
||||
tags.push_back({0, 0});
|
||||
header(2, 2, 6, 0x2000, tags.size() * 16);
|
||||
for (std::size_t index = 0; index < tags.size(); ++index) {
|
||||
Io::WriteU64(bytes, 0x2000 + index * 16, tags[index].first);
|
||||
@@ -246,6 +253,45 @@ void baseRelocations() {
|
||||
reject([&] { (void)builder.BuildBaseRelocations({0x10fff}); });
|
||||
}
|
||||
|
||||
std::size_t fileOffset(const std::vector<std::uint8_t>& pe, std::uint32_t address) {
|
||||
const auto header = Io::ReadU32(pe, 0x3c);
|
||||
const auto sections = header + 24 + Io::ReadU16(pe, header + 20);
|
||||
for (std::size_t index = 0; index < Io::ReadU16(pe, header + 6); ++index) {
|
||||
const auto at = sections + index * 40;
|
||||
const auto start = Io::ReadU32(pe, at + 12);
|
||||
if (address >= start && address - start < Io::ReadU32(pe, at + 16)) return Io::ReadU32(pe, at + 20) + address - start;
|
||||
}
|
||||
throw std::runtime_error("Unmapped PE address");
|
||||
}
|
||||
|
||||
bool exports(const std::vector<std::uint8_t>& pe, std::string_view name) {
|
||||
const auto address = Io::ReadU32(pe, Io::ReadU32(pe, 0x3c) + 24 + 112);
|
||||
if (address == 0) return false;
|
||||
const auto directory = fileOffset(pe, address);
|
||||
const auto names = fileOffset(pe, Io::ReadU32(pe, directory + 32));
|
||||
for (std::size_t index = 0; index < Io::ReadU32(pe, directory + 24); ++index) {
|
||||
if (reinterpret_cast<const char*>(pe.data() + fileOffset(pe, Io::ReadU32(pe, names + index * 4))) == name) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void runtimeDirectories() {
|
||||
TestProgress progress;
|
||||
const Relinker::Conversion converter(progress);
|
||||
Relinker::ConversionOptions options;
|
||||
options.Target = Relinker::TargetSystem::Windows;
|
||||
for (const auto* directory : {"sce_module", "sce_modules", "prx", "Media/Modules"}) {
|
||||
auto source = input();
|
||||
const auto parent = source.ModuleRoot / directory;
|
||||
source.Executable = fixture("needed.prx");
|
||||
source.Modules.push_back({parent / "needed.prx", fixture()});
|
||||
source.RuntimeDirectories.push_back(parent);
|
||||
const auto output = converter.Convert(std::move(source), options);
|
||||
require(output.Modules.size() == 1, "Module was not converted");
|
||||
require(exports(output.Modules.front().Bytes, Relinker::GuestInitializeExport) == (std::string_view(directory) == "Media/Modules"), "Wrong start of a runtime module the executable needs");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -256,6 +302,7 @@ int main() {
|
||||
diagnosticContext();
|
||||
cancellation();
|
||||
baseRelocations();
|
||||
runtimeDirectories();
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
|
||||
@@ -7,6 +7,7 @@ import tempfile
|
||||
|
||||
from test_guest_host_libc import importing
|
||||
from test_guest_intel_trampolines import main_fixture
|
||||
from test_guest_module_dir import pe_export_names
|
||||
from test_guest_module_directories import module_with_symbol, needed_libraries
|
||||
from test_guest_needed_modules import executable_with_needed
|
||||
|
||||
@@ -96,8 +97,9 @@ def main():
|
||||
(case / "Media" / "Modules" / "needed.prx").write_bytes(module_with_symbol(True))
|
||||
result, output = convert(case, windows, ["--module-dir", "Media/Modules", "--executable", "picker/picker.bin"], executable_with_needed())
|
||||
if windows:
|
||||
assert result.returncode == 2 and "Guest modules convert differently for picker/picker.bin" in result.stderr, result.stderr
|
||||
assert not output.exists()
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
assert (output.parent / "picker.picker.bin.exe").exists()
|
||||
assert "__aps5_guest_initialize" in pe_export_names((output.parent / "app0" / "Media" / "Modules" / "needed.prx.guest.prx").read_bytes())
|
||||
else:
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
secondary = output.parent / "picker.picker.bin.elf"
|
||||
|
||||
@@ -192,7 +192,10 @@ def main():
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
needed = case / "app0" / "Media" / "Modules" / "needed.prx.guest.prx"
|
||||
if windows:
|
||||
assert "__aps5_guest_initialize" not in pe_export_names(needed.read_bytes())
|
||||
assert "__aps5_guest_initialize" in pe_export_names(needed.read_bytes())
|
||||
if 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)
|
||||
else:
|
||||
assert "$ORIGIN/app0/Media/Modules/needed.prx.guest.prx" in needed_libraries(output.read_bytes()), needed_libraries(output.read_bytes())
|
||||
|
||||
@@ -230,7 +233,7 @@ def main():
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
needed = case / "app0" / "needed.prx.guest.prx"
|
||||
if windows:
|
||||
assert "__aps5_guest_initialize" not in pe_export_names(needed.read_bytes())
|
||||
assert "__aps5_guest_initialize" in pe_export_names(needed.read_bytes())
|
||||
else:
|
||||
assert "$ORIGIN/app0/needed.prx.guest.prx" in needed_libraries(output.read_bytes()), needed_libraries(output.read_bytes())
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- [Executable guest imports](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) - on Linux and macOS, a relocated executable import resolved to one guest provider requires that module and its dependencies at startup, even when it was discovered through `--module-dir` without a DT_NEEDED entry. Unreferenced symbols and no-op relocations do not require an otherwise deferred module. Console module-start ordering is unmeasured.
|
||||
|
||||
- [Runtime-loaded modules](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded.
|
||||
- [Runtime-loaded modules the executable needs](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) are mapped at startup on Windows and started when the title loads them with `sceKernelLoadStartModule`, with their start arguments, as a Unity player loads `mono-ps4.sprx`. A title that calls such a module before loading it finds its initializers not run; whether the console starts it earlier is not measured. A module the executable only imports from and the title never loads itself gets no `module_start` and no initializers at all under this deferral, and nothing reports it. Linux and macOS still start it at startup.
|
||||
- The executable file that [relinker](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens a console when launched, unless it is relinked with `--windows-gui`, which selects the GUI subsystem. A GUI build started from Explorer has no standard output or error handle; the [entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) gives each missing one `anyps5.log` beside the executable, replacing the previous run's file (#3282). Errors about the executable path or about waiting for the previous LoadExec process are written before the log is opened and are lost when the standard error handle is missing. If the log cannot be created, output to the missing handles is lost.
|
||||
- [Guest import registries](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) - declared host imports of bundled modules are recorded with their module name and relocation target, even when another guest module exports the same stripped NID. No-op relocations are omitted. Registries describe import bindings, not implementation correctness or runtime reachability.
|
||||
- Converted Windows main executables [omit `DYNAMIC_BASE`](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) by default, not only with `--windows-fixed-base`: on the console the executable lies inside the 40-bit window that the title's own `sceLibcMspaceCreate` checks (#3043). Relocations are kept, so if `0x140000000` is taken Windows relocates the image, possibly above that window. Verified only on Windows 10.
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ All switches are disabled by default. `unused-filter` defaults to `0`; `--rpath`
|
||||
| `--rpath <path>` | Set the system library search path. Quote `$ORIGIN` to prevent shell expansion, for example `--rpath '$ORIGIN/libs'` in Bash or PowerShell. Linux guest modules require an absolute path or a path beginning with `$ORIGIN`. Windows requires a nonempty ASCII path and supports `$ORIGIN` as the executable directory. |
|
||||
| `--autorun` | Run the output after conversion, print its exit code, and wait for Enter. Adds executable permissions for Linux output. Requires the target OS and prepared runtime layout. |
|
||||
| `--sce-module-path <path>` | Parent of the module directories, not the module directory itself. Default: input ELF parent; relative input paths resolve from the process working directory. Explicit absolute path: used directly. Explicit relative path (including `.` and `..`): relative to the process working directory. Output path has no effect. Requires one nonempty value; repeats, nonexistent or nondirectory paths, access failures, and combination with `--skip-sce-module` are errors. |
|
||||
| `--module-dir <dir>` | Relink the ELF modules of a directory under the module parent directory, besides `sce_module`/`sce_modules`/`prx`; outputs keep the relative path. A module the executable or another module needs is initialized at startup, any other when the title loads it with `sceKernelLoadStartModule`, with its start arguments (for example Unity plugins in `Media/Plugins`). Repeatable; `.` is the parent itself, without the input ELF. Conflicts with `--skip-sce-module`. |
|
||||
| `--module-dir <dir>` | Relink the ELF modules of a directory under the module parent directory, besides `sce_module`/`sce_modules`/`prx`; outputs keep the relative path. A module another module needs is initialized at startup, and on Linux and macOS also one the executable needs; any other when the title loads it with `sceKernelLoadStartModule`, with its start arguments (for example Unity plugins in `Media/Plugins`, or `mono-ps4.sprx` on Windows). Repeatable; `.` is the parent itself, without the input ELF. Conflicts with `--skip-sce-module`. |
|
||||
| `--executable <path>` | Also convert another executable of the title, which it starts with `sceSystemServiceLoadExec`; the path is relative to the module parent directory, for example `picker/picker.bin`. It is written beside the output, named after the path with `/` replaced by `.` and given the output's extension (`picker.picker.bin.exe`), and uses the same `libs/`, `app0/` and guest modules. It is an error if a guest module converts differently for it. Repeatable. Conflicts with `--skip-sce-module`. |
|
||||
| `--patches <file>` | Apply the byte patches of this file during conversion, see [game patches](#game-patches). Overrides `relinker-patches.toml` discovery. Conflicts with `--no-patches`. |
|
||||
| `--no-patches` | Do not apply patches, and do not load `relinker-patches.toml` from the input directory. |
|
||||
|
||||
Reference in new issue
Block a user