feat(relinker): expose in-memory conversion through a portable core library
This commit is contained in:
parent
5e5eba8922
commit
b42aa8c540
|
|
@ -1,12 +1,8 @@
|
|||
add_executable(relinker
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/CliArgs.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/NativeModules.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp
|
||||
option(ANYPS5_RELINKER_CLI "Build the native relinker command line" ON)
|
||||
|
||||
add_library(relinker_core STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxElfPatcher.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxDesktopEntryWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsGuestStartup.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/guest/GuestImageReader.cpp
|
||||
|
|
@ -34,8 +30,6 @@ add_executable(relinker
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/NativePath.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/Amd64OnlyConverter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/InstructionScanner.cpp
|
||||
|
|
@ -72,29 +66,47 @@ add_executable(relinker
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/ValidationPolicy.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/output/SysVDynamicSectionBuilder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/output/CallRegistryWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/pipeline/Conversion.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/pipeline/RelinkerPipeline.cpp
|
||||
)
|
||||
target_include_directories(relinker PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/include
|
||||
target_include_directories(relinker_core PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/domain/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/codegen/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../libs
|
||||
)
|
||||
|
||||
if(BUILD_TESTING AND WIN32)
|
||||
get_target_property(windowsAliasTestSources relinker SOURCES)
|
||||
list(REMOVE_ITEM windowsAliasTestSources
|
||||
target_compile_features(relinker_core PUBLIC cxx_std_20)
|
||||
set_target_properties(relinker_core PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(relinker_conversion_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/ConversionTests.cpp)
|
||||
target_link_libraries(relinker_conversion_tests PRIVATE relinker_core)
|
||||
add_test(NAME relinker_conversion COMMAND relinker_conversion_tests)
|
||||
set_tests_properties(relinker_conversion PROPERTIES TIMEOUT 20)
|
||||
endif()
|
||||
|
||||
if(NOT ANYPS5_RELINKER_CLI)
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(relinker
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/CliArgs.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp)
|
||||
add_test_executable(windows_guest_export_alias_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/WindowsExportAliasTests.cpp
|
||||
${windowsAliasTestSources})
|
||||
get_target_property(windowsAliasIncludes relinker INCLUDE_DIRECTORIES)
|
||||
target_include_directories(windows_guest_export_alias_tests PRIVATE ${windowsAliasIncludes})
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxDesktopEntryWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/NativeModules.cpp)
|
||||
target_link_libraries(relinker PRIVATE relinker_core)
|
||||
target_include_directories(relinker PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli/include ${CMAKE_CURRENT_SOURCE_DIR}/../libs)
|
||||
set_target_properties(relinker PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
||||
if(BUILD_TESTING AND WIN32)
|
||||
add_test_executable(windows_guest_export_alias_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/WindowsExportAliasTests.cpp)
|
||||
target_link_libraries(windows_guest_export_alias_tests PRIVATE relinker_core)
|
||||
add_test(NAME windows_guest_export_aliases COMMAND windows_guest_export_alias_tests)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <domain/IProgress.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace Domain {
|
||||
|
||||
class InputProgress final : public IProgress {
|
||||
public:
|
||||
InputProgress(IProgress& progress, std::string input) : progress(progress), input(std::move(input)) {}
|
||||
|
||||
void Report(const ProgressEvent& event) override {
|
||||
auto named = event;
|
||||
if (named.Input.empty()) named.Input = input;
|
||||
progress.Report(named);
|
||||
}
|
||||
|
||||
void Message(std::string_view message) override { progress.Message(message); }
|
||||
|
||||
private:
|
||||
IProgress& progress;
|
||||
std::string input;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -3,12 +3,13 @@
|
|||
|
||||
#include <elfpatcher/general/IElfPatcher.hpp>
|
||||
#include <optional>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
class WindowsPePatcher : public IElfPatcher {
|
||||
public:
|
||||
explicit WindowsPePatcher(bool windowsGui = false, std::optional<std::vector<std::uint8_t>> icon = std::nullopt);
|
||||
explicit WindowsPePatcher(bool windowsGui = false, std::optional<std::vector<std::uint8_t>> icon = std::nullopt, std::filesystem::path iconName = "icon0.png");
|
||||
const std::string& GetDiagnostics() const;
|
||||
|
||||
std::vector<std::uint8_t> Patch(const std::vector<std::uint8_t>& sourceElf, const std::vector<Domain::ProgramHeader>& originalHeaders, const Domain::SysVDynamicSection& dynamicSection, std::uint64_t originalPltGotVaddr, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics, const std::vector<Codegen::TrampolineSite>& trampolines) override;
|
||||
|
|
@ -17,6 +18,7 @@ private:
|
|||
bool windowsGui;
|
||||
std::optional<std::vector<std::uint8_t>> icon;
|
||||
std::string diagnostics;
|
||||
std::filesystem::path iconName;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ void writeGotStub(std::vector<PeSection>& sections, const std::uint32_t targetRv
|
|||
|
||||
}
|
||||
|
||||
WindowsPePatcher::WindowsPePatcher(const bool windowsGui, std::optional<std::vector<std::uint8_t>> icon) : windowsGui(windowsGui), icon(std::move(icon)) {
|
||||
WindowsPePatcher::WindowsPePatcher(const bool windowsGui, std::optional<std::vector<std::uint8_t>> icon, std::filesystem::path iconName) : windowsGui(windowsGui), icon(std::move(icon)), iconName(std::move(iconName)) {
|
||||
}
|
||||
|
||||
const std::string& WindowsPePatcher::GetDiagnostics() const { return diagnostics; }
|
||||
|
|
@ -95,7 +95,7 @@ std::vector<std::uint8_t> WindowsPePatcher::Patch(const std::vector<std::uint8_t
|
|||
sections.push_back({".reloc", nextRva, SectionRead | 0x02000040u, std::move(relocationData)});
|
||||
nextRva = AlignRva(nextRva + sections.back().Data.size());
|
||||
}
|
||||
directories[2] = icon ? WindowsIconResourceBuilder().Build(*icon, "icon0.png", sections, nextRva) : PeDirectory{};
|
||||
directories[2] = icon ? WindowsIconResourceBuilder().Build(*icon, iconName, sections, nextRva) : PeDirectory{};
|
||||
return WindowsPeWriter().Write(sections, entryRva, directories, windowsGui);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,15 @@
|
|||
#include <io/NativePath.hpp>
|
||||
#include <domain/Types.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <utility>
|
||||
#endif
|
||||
|
||||
namespace Io {
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
std::filesystem::path NativePath(const std::string& path) {
|
||||
if (path.empty()) return {};
|
||||
const int wideSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path.data(), static_cast<int>(path.size()), nullptr, 0);
|
||||
if (wideSize <= 0) throw Domain::RelinkerException("Path is not valid UTF-8: " + path);
|
||||
std::wstring wide(static_cast<std::size_t>(wideSize), L'\0');
|
||||
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path.data(), static_cast<int>(path.size()), wide.data(), wideSize) != wideSize)
|
||||
throw Domain::RelinkerException("Path is not valid UTF-8: " + path);
|
||||
return std::filesystem::path(std::move(wide));
|
||||
const std::u8string utf8(path.begin(), path.end());
|
||||
return std::filesystem::path(utf8);
|
||||
}
|
||||
|
||||
std::string Utf8Path(const std::filesystem::path& path) {
|
||||
const std::wstring wide = path.wstring();
|
||||
if (wide.empty()) return {};
|
||||
const int utf8Size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide.data(), static_cast<int>(wide.size()), nullptr, 0, nullptr, nullptr);
|
||||
if (utf8Size <= 0) throw Domain::RelinkerException("Path cannot be encoded as UTF-8");
|
||||
std::string utf8(static_cast<std::size_t>(utf8Size), '\0');
|
||||
if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide.data(), static_cast<int>(wide.size()), utf8.data(), utf8Size, nullptr, nullptr) != utf8Size)
|
||||
throw Domain::RelinkerException("Path cannot be encoded as UTF-8");
|
||||
return utf8;
|
||||
const auto utf8 = path.u8string();
|
||||
return std::string(utf8.begin(), utf8.end());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::filesystem::path NativePath(const std::string& path) {
|
||||
return std::filesystem::path(path);
|
||||
}
|
||||
|
||||
std::string Utf8Path(const std::filesystem::path& path) {
|
||||
return path.string();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,13 @@
|
|||
#include <NativeModules.hpp>
|
||||
#include <ConsoleProgress.hpp>
|
||||
#include <NativeModules.hpp>
|
||||
#include <relinker/pipeline/Conversion.hpp>
|
||||
#include <Cli.hpp>
|
||||
#include <domain/Types.hpp>
|
||||
#include <io/FileReader.hpp>
|
||||
#include <io/NativePath.hpp>
|
||||
#include <elfpatcher/linux/LinuxElfPatcher.hpp>
|
||||
#include <elfpatcher/linux/LinuxDesktopEntryWriter.hpp>
|
||||
#include <elfpatcher/general/SegmentFilter.hpp>
|
||||
#include <elfpatcher/general/EntryStubBuilder.hpp>
|
||||
#include <elfpatcher/general/ProgramHeaderLayoutBuilder.hpp>
|
||||
#include <elfpatcher/general/SectionHeaderTableBuilder.hpp>
|
||||
#include <elfpatcher/windows/WindowsElfPatcher.hpp>
|
||||
#include <elfpatcher/macos/MacOsMachOPatcher.hpp>
|
||||
#include <io/ByteWriter.hpp>
|
||||
#include <io/FileWriter.hpp>
|
||||
#include <relinker/parsing/ElfReader.hpp>
|
||||
#include <relinker/analysis/ValidationPolicy.hpp>
|
||||
#include <relinker/analysis/SyscallScanner.hpp>
|
||||
#include <relinker/analysis/CallSiteResolver.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter.hpp>
|
||||
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
|
||||
#include <relinker/output/CallRegistryWriter.hpp>
|
||||
#include <relinker/pipeline/RelinkerPipeline.hpp>
|
||||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <codegen/IAmd64OnlyConverter.hpp>
|
||||
#include <map>
|
||||
#include <codegen/CodegenException.hpp>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
|
@ -43,8 +26,6 @@ namespace {
|
|||
struct ConvertedExecutable {
|
||||
std::string InputPath;
|
||||
std::string OutputPath;
|
||||
std::vector<std::uint8_t> Source;
|
||||
std::vector<Codegen::TrampolineSite> Trampolines;
|
||||
Relinker::RelinkResult Result;
|
||||
std::vector<Relinker::GuestArtifact> GuestArtifacts;
|
||||
std::vector<std::uint8_t> Bytes;
|
||||
|
|
@ -53,93 +34,32 @@ struct ConvertedExecutable {
|
|||
|
||||
ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable, Domain::IProgress& progress) {
|
||||
Io::FileReader fileReader;
|
||||
auto sourceBytes = fileReader.Read(inputPath);
|
||||
|
||||
std::vector<Codegen::TrampolineSite> trampolines;
|
||||
if (args.toIntel) {
|
||||
const auto segments = Relinker::ElfReader(sourceBytes).ReadProgramHeaders();
|
||||
auto converted = Codegen::MakeAmd64OnlyConverter(args.toRosetta ? Codegen::Amd64OnlyTarget::Rosetta : Codegen::Amd64OnlyTarget::Intel)->Convert(std::move(sourceBytes), segments);
|
||||
sourceBytes = std::move(converted.Bytes);
|
||||
trampolines = std::move(converted.Trampolines);
|
||||
std::map<std::string, std::size_t> stubsByName;
|
||||
const auto counted = [](const std::string& name) { return name == "VRSQRTPS" || name == "VRCPPS" || name == "256-bit store"; };
|
||||
for (const auto& report : converted.Reports) {
|
||||
if (report.Lowering == Codegen::Amd64OnlyLowering::Kept)
|
||||
std::cout << "Intel substitution: " << report.InstructionName << " at 0x" << std::hex << report.Offset << std::dec << " (" << report.OriginalLength << " bytes) kept: no room for a jump\n";
|
||||
else if (counted(report.InstructionName))
|
||||
++stubsByName[report.InstructionName];
|
||||
else
|
||||
std::cout << "Intel substitution: " << report.InstructionName << " at 0x" << std::hex << report.Offset << std::dec << " (" << report.OriginalLength << " bytes) -> " << (report.Lowering == Codegen::Amd64OnlyLowering::InPlace ? "in place " : "stub ") << report.ReplacementLength << " bytes\n";
|
||||
}
|
||||
for (const auto& [name, count] : stubsByName)
|
||||
std::cout << "Intel substitution: " << name << " -> stub at " << count << " sites\n";
|
||||
std::cout << "Intel conversion: " << converted.ReplacedCount << " in place, " << trampolines.size() << " stubs, " << converted.KeptCount << " kept\n";
|
||||
}
|
||||
|
||||
auto elfReader = std::make_shared<Relinker::ElfReader>(sourceBytes);
|
||||
const std::shared_ptr<Relinker::ISyscallScanner> syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(progress, args.trapSdkSyscallStubs);
|
||||
|
||||
const auto pipeline = std::make_shared<Relinker::RelinkerPipeline>(
|
||||
elfReader,
|
||||
syscallScanner,
|
||||
Relinker::MakeCallSiteResolver(),
|
||||
std::make_shared<Relinker::ValidationPolicy>(),
|
||||
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
|
||||
args.unusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter(progress) : Relinker::MakeUnusedNidFilter(),
|
||||
args.unusedFilterLevel, progress
|
||||
);
|
||||
|
||||
if (mainExecutable) {
|
||||
std::cout << "System: " << (args.toWindows ? "Windows" : args.toMacos ? "macOS" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n";
|
||||
std::cout << Relinker::GuestModulePattern << " processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n';
|
||||
for (const auto& name : args.excludedSceModules) std::cout << "Guest module excluded: " << name << '\n';
|
||||
}
|
||||
auto result = pipeline->Relink(sourceBytes);
|
||||
for (const auto& patch : result.Patches) {
|
||||
if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset)
|
||||
throw Domain::RelinkerException("Relinker patch exceeds source image", patch.Offset);
|
||||
for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index];
|
||||
}
|
||||
|
||||
std::vector<Relinker::GuestArtifact> guestArtifacts;
|
||||
Relinker::ConversionInput input;
|
||||
input.Executable = fileReader.Read(inputPath);
|
||||
input.InputPath = std::filesystem::absolute(Io::NativePath(inputPath)).lexically_normal();
|
||||
input.OutputPath = Io::NativePath(absPath);
|
||||
if (!args.skipSceModule) {
|
||||
auto modules = Cli::ReadModules(std::filesystem::absolute(Io::NativePath(inputPath)), Io::NativePath(args.sceModulePath), args.excludedSceModules, args.moduleDirectories);
|
||||
guestArtifacts = Relinker::GuestModuleBuilder().Build(std::filesystem::absolute(Io::NativePath(inputPath)), Io::NativePath(absPath), result.DynamicSection, args.toWindows, args.toMacos, args.toIntel, args.toRosetta, *syscallScanner, args.lazyBinding, args.runPath, args.excludedSceModules, modules.Root, modules.RuntimeDirectories, std::move(modules.Modules), progress);
|
||||
auto modules = Cli::ReadModules(input.InputPath, Io::NativePath(args.sceModulePath), args.excludedSceModules, args.moduleDirectories);
|
||||
input.ModuleRoot = std::move(modules.Root);
|
||||
input.Modules = std::move(modules.Modules);
|
||||
input.RuntimeDirectories = std::move(modules.RuntimeDirectories);
|
||||
}
|
||||
|
||||
return {inputPath, absPath, std::move(sourceBytes), std::move(trampolines), std::move(result), std::move(guestArtifacts), {}};
|
||||
}
|
||||
|
||||
void Patch(const Cli::Args& args, ConvertedExecutable& converted, const bool mainExecutable) {
|
||||
auto byteWriter = std::make_shared<Io::ByteWriter>();
|
||||
|
||||
std::shared_ptr<Elfpatcher::IElfPatcher> patcher;
|
||||
if (args.toMacos) {
|
||||
patcher = std::make_shared<Elfpatcher::MacOs::MacOsMachOPatcher>();
|
||||
} else if (args.toWindows) {
|
||||
const auto iconPath = Io::NativePath(args.inputPath).parent_path() / "sce_sys" / "icon0.png";
|
||||
std::optional<std::vector<std::uint8_t>> icon;
|
||||
if (std::filesystem::exists(iconPath)) icon = Io::FileReader().Read(Io::Utf8Path(iconPath));
|
||||
patcher = std::make_shared<Elfpatcher::Windows::WindowsPePatcher>(args.windowsGui, std::move(icon));
|
||||
} else {
|
||||
patcher = std::make_shared<Elfpatcher::Linux::LinuxElfPatcher>(
|
||||
std::make_shared<Elfpatcher::EntryStubBuilder>(),
|
||||
std::make_shared<Elfpatcher::ProgramHeaderLayoutBuilder>(
|
||||
std::make_shared<Elfpatcher::SegmentFilter>(),
|
||||
byteWriter
|
||||
),
|
||||
std::make_shared<Elfpatcher::SectionHeaderTableBuilder>(byteWriter),
|
||||
byteWriter
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
converted.Bytes = patcher->Patch(converted.Source, converted.Result.OriginalHeaders, converted.Result.DynamicSection, converted.Result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics && mainExecutable, converted.Trampolines);
|
||||
} catch (Domain::RelinkerException& error) {
|
||||
error.InputPath = converted.InputPath;
|
||||
throw;
|
||||
}
|
||||
if (args.windowsDiagnostics && mainExecutable) converted.Diagnostics = std::static_pointer_cast<Elfpatcher::Windows::WindowsPePatcher>(patcher)->GetDiagnostics();
|
||||
if (args.toWindows && std::filesystem::exists(iconPath)) input.Icon = fileReader.Read(Io::Utf8Path(iconPath));
|
||||
Relinker::ConversionOptions options;
|
||||
options.Target = args.toWindows ? Relinker::TargetSystem::Windows : args.toMacos ? Relinker::TargetSystem::MacOs : Relinker::TargetSystem::Linux;
|
||||
options.ToIntel = args.toIntel;
|
||||
options.ToRosetta = args.toRosetta;
|
||||
options.SkipSyscallCheck = args.skipSyscallCheck;
|
||||
options.TrapSdkSyscallStubs = args.trapSdkSyscallStubs;
|
||||
options.UnusedFilterLevel = args.unusedFilterLevel;
|
||||
options.LazyBinding = args.lazyBinding;
|
||||
options.RunPath = args.runPath;
|
||||
options.WindowsGui = args.windowsGui;
|
||||
options.WindowsDiagnostics = args.windowsDiagnostics && mainExecutable;
|
||||
options.ExcludedModules = args.excludedSceModules;
|
||||
auto converted = Relinker::Conversion(progress).Convert(std::move(input), options);
|
||||
return {inputPath, absPath, std::move(converted.Analysis), std::move(converted.Modules), std::move(converted.Executable), std::move(converted.WindowsDiagnostics)};
|
||||
}
|
||||
|
||||
int Run(const int argc, char* argv[]) {
|
||||
|
|
@ -249,14 +169,20 @@ int Run(const int argc, char* argv[]) {
|
|||
checkOutputInputs(Io::NativePath(absPath), "Executable");
|
||||
for (const auto& artifact : guestArtifacts) checkOutputInputs(artifact.Path, "Guest module");
|
||||
|
||||
for (std::size_t index = 0; index < executables.size(); ++index) Patch(args, executables[index], index == 0);
|
||||
if (args.windowsDiagnostics) fileWriter.Write("windows-diagnostics-imports.txt", executables.front().Diagnostics);
|
||||
const auto outputCount = guestArtifacts.size() + executables.size();
|
||||
progress.Report({Domain::ProgressStage::Write, absPath, 0, outputCount});
|
||||
std::size_t written = 0;
|
||||
for (const auto& artifact : guestArtifacts) {
|
||||
std::filesystem::create_directories(artifact.Path.parent_path());
|
||||
fileWriter.Write(Io::Utf8Path(artifact.Path), artifact.Bytes);
|
||||
progress.Report({Domain::ProgressStage::Write, absPath, ++written, outputCount});
|
||||
std::cout << "Guest module: " << Io::Utf8Path(artifact.Path) << '\n';
|
||||
}
|
||||
for (const auto& executable : executables) fileWriter.Write(executable.OutputPath, executable.Bytes);
|
||||
for (const auto& executable : executables) {
|
||||
fileWriter.Write(executable.OutputPath, executable.Bytes);
|
||||
progress.Report({Domain::ProgressStage::Write, absPath, ++written, outputCount});
|
||||
}
|
||||
if (args.writeRegistry) {
|
||||
const auto registryWriter = std::make_shared<Relinker::CallRegistryWriter>();
|
||||
for (std::size_t index = 0; index < executables.size(); ++index)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <relinker/domain/Types.hpp>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
|
|
@ -11,11 +12,7 @@ class ISyscallScanner {
|
|||
public:
|
||||
virtual ~ISyscallScanner() = default;
|
||||
|
||||
virtual std::vector<std::size_t> ScanCodeSectionForSyscalls(
|
||||
const std::vector<std::uint8_t>& codeSection,
|
||||
FileByteOffset codeSectionOffset,
|
||||
FileByteOffset codeSectionSize
|
||||
) = 0;
|
||||
virtual std::vector<std::size_t> ScanCodeSectionForSyscalls(const std::vector<std::uint8_t>& codeSection, FileByteOffset codeSectionOffset, FileByteOffset codeSectionSize, std::string_view input = {}) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <domain/IProgress.hpp>
|
||||
#include <relinker/domain/RelinkResult.hpp>
|
||||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
enum class TargetSystem { Linux, Windows, MacOs };
|
||||
|
||||
struct ConversionOptions {
|
||||
TargetSystem Target = TargetSystem::Linux;
|
||||
std::uint32_t UnusedFilterLevel = 0;
|
||||
bool ToIntel = false;
|
||||
bool ToRosetta = false;
|
||||
bool SkipSyscallCheck = false;
|
||||
bool TrapSdkSyscallStubs = false;
|
||||
bool LazyBinding = false;
|
||||
bool WindowsGui = false;
|
||||
bool WindowsDiagnostics = false;
|
||||
std::string RunPath = "$ORIGIN/libs";
|
||||
std::set<std::string> ExcludedModules;
|
||||
};
|
||||
|
||||
struct ConversionInput {
|
||||
std::vector<std::uint8_t> Executable;
|
||||
std::filesystem::path InputPath;
|
||||
std::filesystem::path OutputPath;
|
||||
std::filesystem::path ModuleRoot;
|
||||
std::vector<GuestModuleInput> Modules;
|
||||
std::vector<std::filesystem::path> RuntimeDirectories;
|
||||
std::optional<std::vector<std::uint8_t>> Icon;
|
||||
};
|
||||
|
||||
struct ConversionOutput {
|
||||
std::vector<std::uint8_t> Executable;
|
||||
std::vector<GuestArtifact> Modules;
|
||||
RelinkResult Analysis;
|
||||
std::string WindowsDiagnostics;
|
||||
};
|
||||
|
||||
class Conversion {
|
||||
public:
|
||||
explicit Conversion(Domain::IProgress& progress);
|
||||
ConversionOutput Convert(ConversionInput input, const ConversionOptions& options) const;
|
||||
|
||||
private:
|
||||
Domain::IProgress& progress;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ public:
|
|||
std::vector<std::size_t> ScanCodeSectionForSyscalls(
|
||||
const std::vector<std::uint8_t>& codeSection,
|
||||
FileByteOffset codeSectionOffset,
|
||||
FileByteOffset codeSectionSize) override;
|
||||
FileByteOffset codeSectionSize, std::string_view input) override;
|
||||
|
||||
private:
|
||||
Domain::IProgress& progress;
|
||||
|
|
@ -179,16 +179,16 @@ private:
|
|||
std::vector<std::size_t> SyscallScanner::ScanCodeSectionForSyscalls(
|
||||
const std::vector<std::uint8_t>& codeSection,
|
||||
const FileByteOffset codeSectionOffset,
|
||||
const FileByteOffset codeSectionSize) {
|
||||
const FileByteOffset codeSectionSize, std::string_view input) {
|
||||
const std::size_t limit = std::min(codeSection.size(), static_cast<std::size_t>(codeSectionSize));
|
||||
|
||||
const auto scanner = Codegen::MakeInstructionScanner([&](auto completed, auto total) { progress.Report({Domain::ProgressStage::Decode, {}, completed, total}); });
|
||||
const auto scanner = Codegen::MakeInstructionScanner([&](auto completed, auto total) { progress.Report({Domain::ProgressStage::Decode, std::string(input), completed, total}); });
|
||||
const auto matches = scanner->ScanCodeSection(codeSection, codeSectionOffset, codeSectionSize);
|
||||
|
||||
progress.Report({Domain::ProgressStage::Syscalls, {}, 0, matches.size()});
|
||||
progress.Report({Domain::ProgressStage::Syscalls, std::string(input), 0, matches.size()});
|
||||
std::vector<std::size_t> trapped;
|
||||
for (std::size_t index = 0; index < matches.size(); ++index) {
|
||||
if (index % 4096 == 0) progress.Report({Domain::ProgressStage::Syscalls, {}, index, matches.size()});
|
||||
if (index != 0 && index % 4096 == 0) progress.Report({Domain::ProgressStage::Syscalls, std::string(input), index, matches.size()});
|
||||
const auto& match = matches[index];
|
||||
const std::size_t start = static_cast<std::size_t>(match.Offset - codeSectionOffset);
|
||||
const auto opcodeOffset = Codegen::DecodedInstruction{codeSection.data() + start, match.Length}.OpcodeOffset();
|
||||
|
|
@ -218,7 +218,7 @@ std::vector<std::size_t> SyscallScanner::ScanCodeSectionForSyscalls(
|
|||
|
||||
throw RelinkerException(msg.str(), match.Offset);
|
||||
}
|
||||
progress.Report({Domain::ProgressStage::Syscalls, {}, matches.size(), matches.size()});
|
||||
progress.Report({Domain::ProgressStage::Syscalls, std::string(input), matches.size(), matches.size()});
|
||||
return trapped;
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +231,8 @@ public:
|
|||
std::vector<std::size_t> ScanCodeSectionForSyscalls(
|
||||
const std::vector<std::uint8_t>&,
|
||||
FileByteOffset,
|
||||
FileByteOffset
|
||||
FileByteOffset,
|
||||
std::string_view
|
||||
) override { return {}; }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
progress.Report({Domain::ProgressStage::Modules, {}, 0, modules.size()});
|
||||
for (auto& module : modules) {
|
||||
const auto path = module.Path.lexically_normal();
|
||||
if (path == inputPath.lexically_normal() || path == outputPath.lexically_normal()) throw Domain::RelinkerException("Module path collides with executable: " + Io::Utf8Path(path));
|
||||
if (path == inputPath.lexically_normal()) throw Domain::RelinkerException("Module path collides with the input executable: " + Io::Utf8Path(path));
|
||||
if (path == outputPath.lexically_normal()) throw Domain::RelinkerException("Executable output would overwrite an input module: " + Io::Utf8Path(path));
|
||||
const auto relative = path.lexically_relative(root);
|
||||
if (relative.empty() || relative.has_root_path() || *relative.begin() == "..") throw Domain::RelinkerException("Module is outside module root: " + Io::Utf8Path(path));
|
||||
if (excludedModules.contains(Io::Utf8Path(path.filename()))) throw Domain::RelinkerException("Excluded module supplied to converter: " + Io::Utf8Path(path));
|
||||
|
|
@ -299,7 +300,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
}
|
||||
for (const auto& header : codeHeaders) {
|
||||
const std::vector<std::uint8_t> code(image.Bytes.begin() + header.Offset, image.Bytes.begin() + header.Offset + header.FileSize);
|
||||
for (const auto offset : syscallScanner.ScanCodeSectionForSyscalls(code, header.MappedAddress, header.FileSize)) {
|
||||
for (const auto offset : syscallScanner.ScanCodeSectionForSyscalls(code, header.MappedAddress, header.FileSize, Io::Utf8Path(image.SourcePath))) {
|
||||
if (offset > code.size() || code.size() - offset < 2u)
|
||||
throw Domain::RelinkerException("Syscall trap patch exceeds guest executable segment", header.MappedAddress + offset);
|
||||
image.Bytes[header.Offset + offset] = 0x0F;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
#include <domain/Types.hpp>
|
||||
#include <io/NativePath.hpp>
|
||||
#include <elfpatcher/linux/LinuxElfPatcher.hpp>
|
||||
#include <elfpatcher/general/SegmentFilter.hpp>
|
||||
#include <elfpatcher/general/EntryStubBuilder.hpp>
|
||||
#include <elfpatcher/general/ProgramHeaderLayoutBuilder.hpp>
|
||||
#include <elfpatcher/general/SectionHeaderTableBuilder.hpp>
|
||||
#include <elfpatcher/windows/WindowsElfPatcher.hpp>
|
||||
#include <elfpatcher/macos/MacOsMachOPatcher.hpp>
|
||||
#include <io/ByteWriter.hpp>
|
||||
#include <relinker/parsing/ElfReader.hpp>
|
||||
#include <relinker/analysis/ValidationPolicy.hpp>
|
||||
#include <relinker/analysis/SyscallScanner.hpp>
|
||||
#include <relinker/analysis/CallSiteResolver.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter.hpp>
|
||||
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
|
||||
#include <relinker/output/CallRegistryWriter.hpp>
|
||||
#include <relinker/pipeline/RelinkerPipeline.hpp>
|
||||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <codegen/IAmd64OnlyConverter.hpp>
|
||||
#include <map>
|
||||
#include <codegen/CodegenException.hpp>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <relinker/pipeline/Conversion.hpp>
|
||||
#include <sstream>
|
||||
#include <bit>
|
||||
#include <domain/InputProgress.hpp>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
Conversion::Conversion(Domain::IProgress& progress) : progress(progress) {}
|
||||
|
||||
ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOptions& options) const {
|
||||
Domain::InputProgress inputProgress(progress, Io::Utf8Path(input.InputPath));
|
||||
static_assert(std::endian::native == std::endian::little);
|
||||
if (options.Target != TargetSystem::Linux && options.Target != TargetSystem::Windows && options.Target != TargetSystem::MacOs) throw Domain::RelinkerException("Unknown target system");
|
||||
if (options.ToRosetta && (!options.ToIntel || options.Target == TargetSystem::Windows)) throw Domain::RelinkerException("Rosetta conversion requires Intel lowering and a non-Windows target");
|
||||
if (options.UnusedFilterLevel > 2) throw Domain::RelinkerException("Unused NID filter level must be 0, 1 or 2");
|
||||
if (input.InputPath.empty() || input.OutputPath.empty()) throw Domain::RelinkerException("Input and output names are required");
|
||||
if (input.InputPath.lexically_normal() == input.OutputPath.lexically_normal()) throw Domain::RelinkerException("Executable output would overwrite the input executable");
|
||||
if (!input.Modules.empty() && input.ModuleRoot.empty()) throw Domain::RelinkerException("Module root is required");
|
||||
if (options.WindowsDiagnostics && options.Target != TargetSystem::Windows) throw Domain::RelinkerException("Windows diagnostics require the Windows target");
|
||||
const bool windows = options.Target == TargetSystem::Windows;
|
||||
const bool macos = options.Target == TargetSystem::MacOs;
|
||||
inputProgress.Report({Domain::ProgressStage::Executable, Io::Utf8Path(input.InputPath), 0, 1});
|
||||
auto sourceBytes = std::move(input.Executable);
|
||||
std::vector<Codegen::TrampolineSite> trampolines;
|
||||
if (options.ToIntel) {
|
||||
const auto segments = Relinker::ElfReader(sourceBytes).ReadProgramHeaders();
|
||||
auto converted = Codegen::MakeAmd64OnlyConverter(options.ToRosetta ? Codegen::Amd64OnlyTarget::Rosetta : Codegen::Amd64OnlyTarget::Intel)->Convert(std::move(sourceBytes), segments);
|
||||
sourceBytes = std::move(converted.Bytes);
|
||||
trampolines = std::move(converted.Trampolines);
|
||||
std::map<std::string, std::size_t> stubsByName;
|
||||
const auto counted = [](const std::string& name) { return name == "VRSQRTPS" || name == "VRCPPS" || name == "256-bit store"; };
|
||||
for (const auto& report : converted.Reports) {
|
||||
if (report.Lowering == Codegen::Amd64OnlyLowering::Kept)
|
||||
{ std::ostringstream message; message << "Intel substitution: " << report.InstructionName << " at 0x" << std::hex << report.Offset << std::dec << " (" << report.OriginalLength << " bytes) kept: no room for a jump\n"; inputProgress.Message(message.str()); }
|
||||
else if (counted(report.InstructionName))
|
||||
++stubsByName[report.InstructionName];
|
||||
else
|
||||
{ std::ostringstream message; message << "Intel substitution: " << report.InstructionName << " at 0x" << std::hex << report.Offset << std::dec << " (" << report.OriginalLength << " bytes) -> " << (report.Lowering == Codegen::Amd64OnlyLowering::InPlace ? "in place " : "stub ") << report.ReplacementLength << " bytes\n"; inputProgress.Message(message.str()); }
|
||||
}
|
||||
for (const auto& [name, count] : stubsByName)
|
||||
{ std::ostringstream message; message << "Intel substitution: " << name << " -> stub at " << count << " sites\n"; inputProgress.Message(message.str()); }
|
||||
{ std::ostringstream message; message << "Intel conversion: " << converted.ReplacedCount << " in place, " << trampolines.size() << " stubs, " << converted.KeptCount << " kept\n"; inputProgress.Message(message.str()); }
|
||||
}
|
||||
|
||||
auto elfReader = std::make_shared<Relinker::ElfReader>(sourceBytes);
|
||||
const std::shared_ptr<Relinker::ISyscallScanner> syscallScanner = options.SkipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(inputProgress, options.TrapSdkSyscallStubs);
|
||||
|
||||
const auto pipeline = std::make_shared<Relinker::RelinkerPipeline>(
|
||||
elfReader,
|
||||
syscallScanner,
|
||||
Relinker::MakeCallSiteResolver(),
|
||||
std::make_shared<Relinker::ValidationPolicy>(),
|
||||
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
|
||||
options.UnusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter(inputProgress) : Relinker::MakeUnusedNidFilter(),
|
||||
options.UnusedFilterLevel, inputProgress
|
||||
);
|
||||
|
||||
{ std::ostringstream message; message << "System: " << (windows ? "Windows" : macos ? "macOS" : "Linux") << "; unused-filter=" << options.UnusedFilterLevel << "\n"; inputProgress.Message(message.str()); }
|
||||
for (const auto& name : options.ExcludedModules) { std::ostringstream message; message << "Guest module excluded: " << name << '\n'; inputProgress.Message(message.str()); }
|
||||
auto result = pipeline->Relink(sourceBytes);
|
||||
for (const auto& patch : result.Patches) {
|
||||
if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset)
|
||||
throw Domain::RelinkerException("Relinker patch exceeds source image", patch.Offset);
|
||||
for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index];
|
||||
}
|
||||
|
||||
std::vector<Relinker::GuestArtifact> guestArtifacts;
|
||||
if (!input.Modules.empty()) {
|
||||
guestArtifacts = Relinker::GuestModuleBuilder().Build(input.InputPath, input.OutputPath, result.DynamicSection, windows, macos, options.ToIntel, options.ToRosetta, *syscallScanner, options.LazyBinding, options.RunPath, options.ExcludedModules, input.ModuleRoot, input.RuntimeDirectories, std::move(input.Modules), inputProgress);
|
||||
}
|
||||
|
||||
auto byteWriter = std::make_shared<Io::ByteWriter>();
|
||||
|
||||
std::shared_ptr<Elfpatcher::IElfPatcher> patcher;
|
||||
if (macos) {
|
||||
patcher = std::make_shared<Elfpatcher::MacOs::MacOsMachOPatcher>();
|
||||
} else if (windows) {
|
||||
patcher = std::make_shared<Elfpatcher::Windows::WindowsPePatcher>(options.WindowsGui, std::move(input.Icon), input.InputPath.parent_path() / "sce_sys" / "icon0.png");
|
||||
} else {
|
||||
patcher = std::make_shared<Elfpatcher::Linux::LinuxElfPatcher>(
|
||||
std::make_shared<Elfpatcher::EntryStubBuilder>(),
|
||||
std::make_shared<Elfpatcher::ProgramHeaderLayoutBuilder>(
|
||||
std::make_shared<Elfpatcher::SegmentFilter>(),
|
||||
byteWriter
|
||||
),
|
||||
std::make_shared<Elfpatcher::SectionHeaderTableBuilder>(byteWriter),
|
||||
byteWriter
|
||||
);
|
||||
}
|
||||
|
||||
inputProgress.Report({Domain::ProgressStage::Image, Io::Utf8Path(input.InputPath), 0, 1});
|
||||
std::vector<std::uint8_t> executableBytes;
|
||||
try {
|
||||
executableBytes = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, options.RunPath, options.LazyBinding, options.WindowsDiagnostics, trampolines);
|
||||
} catch (Domain::RelinkerException& error) {
|
||||
error.InputPath = Io::Utf8Path(input.InputPath);
|
||||
throw;
|
||||
}
|
||||
|
||||
inputProgress.Report({Domain::ProgressStage::Image, Io::Utf8Path(input.InputPath), 1, 1});
|
||||
std::string diagnostics;
|
||||
if (windows) diagnostics = std::static_pointer_cast<Elfpatcher::Windows::WindowsPePatcher>(patcher)->GetDiagnostics();
|
||||
inputProgress.Report({Domain::ProgressStage::Executable, Io::Utf8Path(input.InputPath), 1, 1});
|
||||
return {std::move(executableBytes), std::move(guestArtifacts), std::move(result), std::move(diagnostics)};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
#include "TestProgress.hpp"
|
||||
#include <relinker/pipeline/Conversion.hpp>
|
||||
#include <relinker/analysis/SyscallScanner.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
void require(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
void reject(TAction action) {
|
||||
try { action(); } catch (const Domain::RelinkerException&) { return; }
|
||||
throw std::runtime_error("Invalid conversion was accepted");
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> fixture() {
|
||||
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());
|
||||
Io::WriteU16(bytes, 16, 3);
|
||||
Io::WriteU16(bytes, 18, 62);
|
||||
Io::WriteU32(bytes, 20, 1);
|
||||
Io::WriteU64(bytes, 24, 0x1000);
|
||||
Io::WriteU64(bytes, 32, 64);
|
||||
Io::WriteU16(bytes, 52, 64);
|
||||
Io::WriteU16(bytes, 54, 56);
|
||||
Io::WriteU16(bytes, 56, 8);
|
||||
Io::WriteU16(bytes, 58, 64);
|
||||
const auto header = [&](std::size_t index, unsigned type, unsigned flags, unsigned offset, unsigned size) {
|
||||
const auto at = 64 + index * 56;
|
||||
Io::WriteU32(bytes, at, type);
|
||||
Io::WriteU32(bytes, at + 4, flags);
|
||||
Io::WriteU64(bytes, at + 8, offset);
|
||||
Io::WriteU64(bytes, at + 16, offset);
|
||||
Io::WriteU64(bytes, at + 24, offset);
|
||||
Io::WriteU64(bytes, at + 32, size);
|
||||
Io::WriteU64(bytes, at + 40, size);
|
||||
Io::WriteU64(bytes, at + 48, 8);
|
||||
};
|
||||
header(0, 1, 5, 0x1000, 8);
|
||||
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}}};
|
||||
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);
|
||||
Io::WriteU64(bytes, 0x2008 + index * 16, tags[index].second);
|
||||
}
|
||||
Io::WriteU32(bytes, 0x2680, 1);
|
||||
Io::WriteU32(bytes, 0x2684, 1);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Relinker::ConversionInput input() {
|
||||
Relinker::ConversionInput result;
|
||||
result.Executable = fixture();
|
||||
result.InputPath = "virtual/title/custom-name.bin";
|
||||
result.OutputPath = "virtual/output/title";
|
||||
result.ModuleRoot = "virtual/title";
|
||||
return result;
|
||||
}
|
||||
|
||||
void convert() {
|
||||
TestProgress progress;
|
||||
const Relinker::Conversion converter(progress);
|
||||
for (const auto target : {Relinker::TargetSystem::Windows, Relinker::TargetSystem::Linux}) {
|
||||
Relinker::ConversionOptions options;
|
||||
options.Target = target;
|
||||
auto source = input();
|
||||
source.Modules.push_back({"virtual/title/sce_module/plugin.prx", fixture()});
|
||||
const auto output = converter.Convert(std::move(source), options);
|
||||
require(output.Executable.size() > 64, "No executable returned");
|
||||
require(output.Executable.front() == (target == Relinker::TargetSystem::Windows ? 'M' : 0x7f), "Wrong target format");
|
||||
require(output.Modules.size() == 1, "Module was not converted");
|
||||
require(output.Modules.front().Path.generic_string() == "virtual/output/app0/sce_module/plugin.prx.guest.prx", "Wrong module layout");
|
||||
require(output.Modules.front().Bytes.front() == output.Executable.front(), "Wrong module format");
|
||||
require(progress.Events.back().Stage == Domain::ProgressStage::Executable && progress.Events.back().Completed == 1, "Conversion did not finish");
|
||||
for (const auto& event : progress.Events) require(event.Completed <= event.Total, "Invalid counters");
|
||||
}
|
||||
auto source = input();
|
||||
source.Modules.push_back({"virtual/escaped.prx", fixture()});
|
||||
reject([&] { converter.Convert(source, {}); });
|
||||
source = input();
|
||||
source.Modules.push_back({"virtual/title/sce_module/plugin.prx", fixture()});
|
||||
source.Modules.push_back(source.Modules.front());
|
||||
reject([&] { converter.Convert(source, {}); });
|
||||
source = input();
|
||||
source.Icon = std::vector<std::uint8_t>{};
|
||||
Relinker::ConversionOptions windows;
|
||||
windows.Target = Relinker::TargetSystem::Windows;
|
||||
reject([&] { converter.Convert(source, windows); });
|
||||
source = input();
|
||||
source.Executable = {};
|
||||
reject([&] { converter.Convert(source, {}); });
|
||||
require(progress.Events.back().Completed != 1 || progress.Events.back().Stage != Domain::ProgressStage::Executable, "Failed conversion reported completion");
|
||||
}
|
||||
|
||||
void scanning() {
|
||||
TestProgress progress;
|
||||
const auto scanner = Relinker::MakeSyscallScanner(progress);
|
||||
std::vector<std::uint8_t> code(150000, 0x90);
|
||||
require(scanner->ScanCodeSectionForSyscalls(code, 0, code.size(), "sample.prx").empty(), "Unexpected syscall trap");
|
||||
require(std::any_of(progress.Events.begin(), progress.Events.end(), [](const auto& event) { return event.Completed > 0 && event.Completed < event.Total; }), "Only start and finish were reported");
|
||||
for (const auto& event : progress.Events) require(event.Input == "sample.prx", "Lost scanner input identity");
|
||||
code[70000] = 0x0f;
|
||||
code[70001] = 0x05;
|
||||
progress.Events.clear();
|
||||
reject([&] { scanner->ScanCodeSectionForSyscalls(code, 0, code.size()); });
|
||||
require(progress.Events.back().Completed < progress.Events.back().Total, "Failed scan reported completion");
|
||||
}
|
||||
|
||||
class CancellingProgress final : public Domain::IProgress {
|
||||
public:
|
||||
void Report(const Domain::ProgressEvent& event) override {
|
||||
if (event.Stage == Domain::ProgressStage::Decode && event.Completed > 0) throw std::runtime_error("cancelled");
|
||||
}
|
||||
void Message(std::string_view) override {}
|
||||
};
|
||||
|
||||
void cancellation() {
|
||||
CancellingProgress progress;
|
||||
try { Relinker::Conversion(progress).Convert(input(), {}); }
|
||||
catch (const std::runtime_error& error) {
|
||||
require(std::string(error.what()) == "cancelled", "Callback exception was replaced");
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("Callback exception was swallowed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
convert();
|
||||
scanning();
|
||||
cancellation();
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue