mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Fresh outputs whose names differ only in case, or that the volume treats as the same name otherwise, were not detected outside Windows. On default macOS APFS, for example, Ä/ä, Ω/ω, Ж/ж and canonically equivalent NFC/NFD names refer to the same file, so one output silently overwrote another. Outside Windows, create the first name in an identity directory next to the outputs and check whether the second name resolves to it. This lets the filesystem decide instead of folding text, so case-sensitive volumes still report distinct names.
539 lines
31 KiB
C++
539 lines
31 KiB
C++
#include <ConsoleProgress.hpp>
|
|
#include <RuntimeLayout.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/LinuxDesktopEntryWriter.hpp>
|
|
#include <io/FileWriter.hpp>
|
|
#include <relinker/output/CallRegistryWriter.hpp>
|
|
#include <codegen/CodegenException.hpp>
|
|
#include <relinker/patching/GamePatch.hpp>
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <random>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <cwchar>
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
struct ConvertedExecutable {
|
|
std::string InputPath;
|
|
std::string OutputPath;
|
|
Relinker::RelinkResult Result;
|
|
std::vector<Relinker::GuestArtifact> GuestArtifacts;
|
|
std::vector<std::uint8_t> Bytes;
|
|
std::string Diagnostics;
|
|
std::vector<Relinker::GamePatch::AppliedPatch> AppliedPatches;
|
|
};
|
|
|
|
ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable, const std::string& targetName, const std::vector<Relinker::GamePatch::PatchEntry>& patches, Cli::ConsoleProgress& progress) {
|
|
Io::FileReader fileReader;
|
|
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(args.inputPath)).lexically_normal(), 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);
|
|
}
|
|
const auto iconPath = Io::NativePath(args.inputPath).parent_path() / "sce_sys" / "icon0.png";
|
|
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.TraceImports = args.traceImports;
|
|
options.WindowsFixedBase = mainExecutable ? args.windowsFixedBase : std::nullopt;
|
|
options.WindowsDiagnostics = args.windowsDiagnostics && mainExecutable;
|
|
options.LinuxFixedBase = args.linuxFixedBase;
|
|
options.ExcludedModules = args.excludedSceModules;
|
|
options.Patches = patches;
|
|
options.TargetName = targetName;
|
|
progress.IncludeFile(Io::Utf8Path(input.InputPath));
|
|
progress.IncludeFile(Io::Utf8Path(input.OutputPath));
|
|
for (const auto& module : input.Modules) progress.IncludeFile(Io::Utf8Path(module.Path));
|
|
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), std::move(converted.AppliedPatches)};
|
|
}
|
|
|
|
class OutputIdentityDirectory {
|
|
public:
|
|
OutputIdentityDirectory(const OutputIdentityDirectory&) = delete;
|
|
OutputIdentityDirectory& operator=(const OutputIdentityDirectory&) = delete;
|
|
|
|
explicit OutputIdentityDirectory(const std::filesystem::path& parent) {
|
|
std::random_device random;
|
|
for (unsigned attempt = 0; attempt < 64; ++attempt) {
|
|
const auto candidate = parent / (".anyps5-output-identity-" + std::to_string(random()) + "-" + std::to_string(random()));
|
|
std::error_code error;
|
|
if (std::filesystem::create_directory(candidate, error)) {
|
|
directory = candidate;
|
|
return;
|
|
}
|
|
if (error && error != std::errc::file_exists)
|
|
throw std::filesystem::filesystem_error("Cannot create output identity directory", candidate, error);
|
|
}
|
|
throw Domain::RelinkerException("Cannot reserve an output identity directory in " + Io::Utf8Path(parent));
|
|
}
|
|
|
|
~OutputIdentityDirectory() {
|
|
if (directory.empty()) return;
|
|
try {
|
|
Remove();
|
|
} catch (const std::exception& error) {
|
|
std::cerr << "FAIL: Output identity cleanup: " << error.what() << '\n';
|
|
}
|
|
}
|
|
|
|
std::filesystem::path GetPath() const { return directory / "file"; }
|
|
const std::filesystem::path& GetDirectory() const { return directory; }
|
|
|
|
void Remove() {
|
|
std::filesystem::remove(GetPath());
|
|
std::filesystem::remove(directory);
|
|
directory.clear();
|
|
}
|
|
|
|
private:
|
|
std::filesystem::path directory;
|
|
};
|
|
|
|
bool SameOutputFile(const std::filesystem::path& first, const std::filesystem::path& second) {
|
|
if (std::filesystem::equivalent(first, second)) return true;
|
|
#ifdef _WIN32
|
|
if (std::filesystem::status(first).permissions() != std::filesystem::status(second).permissions() &&
|
|
std::filesystem::hard_link_count(first) > 1 && std::filesystem::hard_link_count(second) > 1) {
|
|
for (const auto& path : {first, second}) {
|
|
if ((std::filesystem::status(path).permissions() & std::filesystem::perms::owner_write) == std::filesystem::perms::none)
|
|
throw Domain::RelinkerException("Cannot compare read-only output identities: " + Io::Utf8Path(path));
|
|
}
|
|
OutputIdentityDirectory firstDirectory(first.parent_path());
|
|
OutputIdentityDirectory secondDirectory(second.parent_path());
|
|
std::filesystem::create_hard_link(first, firstDirectory.GetPath());
|
|
std::filesystem::create_hard_link(second, secondDirectory.GetPath());
|
|
const bool same = std::filesystem::equivalent(firstDirectory.GetPath(), secondDirectory.GetPath());
|
|
secondDirectory.Remove();
|
|
firstDirectory.Remove();
|
|
return same;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool SameOutputDestination(const std::filesystem::path& first, const std::filesystem::path& second) {
|
|
const auto firstPath = std::filesystem::weakly_canonical(first);
|
|
const auto secondPath = std::filesystem::weakly_canonical(second);
|
|
if (firstPath == secondPath) return true;
|
|
if (std::filesystem::exists(first) && std::filesystem::exists(second)) return SameOutputFile(first, second);
|
|
#ifdef _WIN32
|
|
auto firstName = Io::Utf8Path(firstPath.filename());
|
|
auto secondName = Io::Utf8Path(secondPath.filename());
|
|
for (auto* name : {&firstName, &secondName})
|
|
for (auto& character : *name)
|
|
if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
|
|
if (firstName == secondName && std::filesystem::exists(firstPath.parent_path()) &&
|
|
std::filesystem::exists(secondPath.parent_path()) && std::filesystem::equivalent(firstPath.parent_path(), secondPath.parent_path())) {
|
|
OutputIdentityDirectory probe(firstPath.parent_path());
|
|
auto alternateName = Io::Utf8Path(probe.GetDirectory().filename());
|
|
for (auto& character : alternateName)
|
|
if (character >= 'a' && character <= 'z') character = static_cast<char>(character - ('a' - 'A'));
|
|
const auto alternate = firstPath.parent_path() / Io::NativePath(alternateName);
|
|
const bool same = std::filesystem::exists(alternate) && std::filesystem::equivalent(probe.GetDirectory(), alternate);
|
|
probe.Remove();
|
|
return same;
|
|
}
|
|
#else
|
|
if (std::filesystem::exists(firstPath.parent_path()) && std::filesystem::exists(secondPath.parent_path()) &&
|
|
std::filesystem::equivalent(firstPath.parent_path(), secondPath.parent_path())) {
|
|
OutputIdentityDirectory probe(firstPath.parent_path());
|
|
const auto name = probe.GetDirectory() / firstPath.filename();
|
|
const auto alternate = probe.GetDirectory() / secondPath.filename();
|
|
std::filesystem::create_directory(name);
|
|
const bool same = std::filesystem::exists(alternate) && std::filesystem::equivalent(name, alternate);
|
|
std::filesystem::remove(name);
|
|
probe.Remove();
|
|
return same;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
std::string ReadText(const std::string& path) {
|
|
const auto bytes = Io::FileReader().Read(path);
|
|
return std::string(bytes.begin(), bytes.end());
|
|
}
|
|
|
|
std::filesystem::path ExecutableRelative(const std::string& executable) {
|
|
const auto relative = Io::NativePath(executable).lexically_normal();
|
|
if (relative.empty() || relative.has_root_path() || *relative.begin() == ".." || !relative.has_filename())
|
|
throw Domain::RelinkerException("--executable must be a file inside the guest module parent directory: " + executable);
|
|
return relative;
|
|
}
|
|
|
|
std::vector<std::filesystem::path> ModuleDirectories(const Cli::Args& args, const std::filesystem::path& moduleRoot) {
|
|
std::vector<std::filesystem::path> moduleDirectories;
|
|
for (const auto* name : {"sce_module", "sce_modules", "prx"}) moduleDirectories.push_back(moduleRoot / name);
|
|
for (const auto& name : args.moduleDirectories) moduleDirectories.push_back((moduleRoot / Io::NativePath(name)).lexically_normal());
|
|
for (auto& directory : moduleDirectories) if (!directory.has_filename()) directory = directory.parent_path();
|
|
return moduleDirectories;
|
|
}
|
|
|
|
std::filesystem::path ResolveExtraExecutable(const std::filesystem::path& moduleRoot, const std::vector<std::filesystem::path>& moduleDirectories, const std::filesystem::path& relative, const std::string& executable) {
|
|
const auto input = moduleRoot / relative;
|
|
if (!std::filesystem::is_regular_file(input)) throw Domain::RelinkerException("--executable is not a file: " + Io::Utf8Path(input));
|
|
if (std::any_of(moduleDirectories.begin(), moduleDirectories.end(), [&](const auto& directory) { return Cli::SameDirectory(directory, input.parent_path()); }))
|
|
throw Domain::RelinkerException("--executable is in a guest module directory: " + executable);
|
|
return input;
|
|
}
|
|
|
|
std::vector<Relinker::GamePatch::PatchEntry> LoadPatches(const Cli::Args& args) {
|
|
if (args.noPatches) return {};
|
|
std::filesystem::path path;
|
|
if (!args.patchesPath.empty()) {
|
|
path = Io::NativePath(args.patchesPath);
|
|
if (!std::filesystem::is_regular_file(path)) throw Domain::RelinkerException("Patch file not found: " + args.patchesPath);
|
|
} else {
|
|
path = Io::NativePath(args.inputPath).parent_path() / Relinker::GamePatch::PatchFileName;
|
|
if (!std::filesystem::is_regular_file(path)) return {};
|
|
}
|
|
std::cout << "Patches: " << Io::Utf8Path(path) << '\n';
|
|
return Relinker::GamePatch::ParsePatchFile(ReadText(Io::Utf8Path(path))).Entries;
|
|
}
|
|
|
|
int RunVerifyPatches(const Cli::Args& args) {
|
|
using namespace Relinker::GamePatch;
|
|
const auto file = ParsePatchFile(ReadText(args.verifyPatchesPath));
|
|
Io::FileReader reader;
|
|
std::vector<VerifyTarget> executables;
|
|
std::vector<VerifyTarget> modules;
|
|
const auto inputPath = Io::NativePath(args.inputPath);
|
|
if (!std::filesystem::is_regular_file(inputPath))
|
|
throw Domain::RelinkerException((args.verifyOutput ? "output not found: " : "input not found: ") + args.inputPath);
|
|
if (args.verifyOutput) {
|
|
executables.push_back({file.MainTarget.value_or(std::string()), reader.Read(args.inputPath)});
|
|
for (const auto& executable : args.executables) {
|
|
const auto relative = ExecutableRelative(executable);
|
|
const auto output = OutputExecutablePath(inputPath, executable);
|
|
if (!std::filesystem::is_regular_file(output)) throw Domain::RelinkerException("output not found: " + Io::Utf8Path(output));
|
|
executables.push_back({relative.generic_string(), reader.Read(Io::Utf8Path(output))});
|
|
}
|
|
} else {
|
|
executables.push_back({Io::Utf8Path(inputPath.filename()), reader.Read(args.inputPath)});
|
|
std::filesystem::path moduleRoot = args.executables.empty() ? std::filesystem::path() : std::filesystem::absolute(Io::NativePath(args.sceModulePath)).lexically_normal();
|
|
if (!args.skipSceModule) {
|
|
auto native = Cli::ReadModules(std::filesystem::absolute(inputPath).lexically_normal(), Io::NativePath(args.sceModulePath), args.excludedSceModules, args.moduleDirectories);
|
|
for (auto& module : native.Modules) modules.push_back({ModuleTargetName(module.Path, native.Root), std::move(module.Bytes)});
|
|
}
|
|
const auto moduleDirectories = ModuleDirectories(args, moduleRoot);
|
|
for (const auto& executable : args.executables) {
|
|
const auto relative = ExecutableRelative(executable);
|
|
const auto input = ResolveExtraExecutable(moduleRoot, moduleDirectories, relative, executable);
|
|
executables.push_back({relative.generic_string(), reader.Read(Io::Utf8Path(input))});
|
|
}
|
|
}
|
|
const auto results = VerifyPatches(file, executables, modules, args.verifyOutput);
|
|
std::size_t mismatches = 0;
|
|
std::size_t notChecked = 0;
|
|
for (const auto& result : results) {
|
|
std::cout << StatusName(result.Status) << ": " << (result.Name.empty() ? std::string() : result.Name + " ") << result.Target << " va=0x" << std::hex << result.VirtualAddress;
|
|
if (result.Status != VerifyStatus::NotChecked) std::cout << " off=0x" << result.Offset;
|
|
std::cout << std::dec << ' ' << (result.Status == VerifyStatus::NotChecked ? "(" + result.Detail + ")" : result.Detail) << '\n';
|
|
if (result.Status == VerifyStatus::Mismatch) ++mismatches;
|
|
if (result.Status == VerifyStatus::NotChecked) ++notChecked;
|
|
}
|
|
std::cout << "Verified " << results.size() << " patches: " << results.size() - mismatches - notChecked << " ok, " << notChecked << " not-checked, " << mismatches << " mismatch\n";
|
|
return mismatches == 0 ? 0 : 1;
|
|
}
|
|
|
|
int Run(const int argc, char* argv[]) {
|
|
Cli::Args args;
|
|
try {
|
|
args = Cli::ParseArgs(argc, argv);
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "FAIL: " << e.what() << "\n";
|
|
return 1;
|
|
}
|
|
|
|
if (args.showHelp) {
|
|
std::cout << Cli::Usage() << '\n';
|
|
return 0;
|
|
}
|
|
|
|
Cli::ConsoleProgress progress(std::cout);
|
|
try {
|
|
if (!args.verifyPatchesPath.empty()) return RunVerifyPatches(args);
|
|
auto extension = Io::Utf8Path(Io::NativePath(args.outputPath).extension());
|
|
for (auto& character : extension) if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
|
|
if (!args.toWindows && !args.toMacos && 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::FileWriter fileWriter;
|
|
|
|
if (std::filesystem::exists(Io::NativePath(args.inputPath)) && std::filesystem::exists(Io::NativePath(args.outputPath)) && SameOutputFile(Io::NativePath(args.inputPath), Io::NativePath(args.outputPath)))
|
|
throw Domain::RelinkerException("Executable output would overwrite the input executable");
|
|
if (Io::NativePath(args.outputPath).filename().empty() || std::filesystem::is_directory(Io::NativePath(args.outputPath)))
|
|
throw Domain::RelinkerException("Executable output is a directory: " + args.outputPath);
|
|
const std::string absPath = Io::Utf8Path(std::filesystem::absolute(Io::NativePath(args.outputPath)));
|
|
|
|
struct ExtraExecutable {
|
|
std::string Argument;
|
|
std::filesystem::path Relative;
|
|
std::filesystem::path Input;
|
|
std::filesystem::path Output;
|
|
};
|
|
std::vector<ExtraExecutable> extraExecutables;
|
|
const auto outputDirectory = Io::NativePath(absPath).parent_path();
|
|
const auto moduleRoot = args.executables.empty() ? std::filesystem::path() : std::filesystem::absolute(Io::NativePath(args.sceModulePath)).lexically_normal();
|
|
const auto moduleDirectories = ModuleDirectories(args, moduleRoot);
|
|
for (const auto& executable : args.executables) {
|
|
const auto relative = ExecutableRelative(executable);
|
|
const auto input = ResolveExtraExecutable(moduleRoot, moduleDirectories, relative, executable);
|
|
const auto output = Relinker::GamePatch::OutputExecutablePath(Io::NativePath(absPath), executable);
|
|
if (std::filesystem::equivalent(Io::NativePath(args.inputPath), input)) throw Domain::RelinkerException("Executable is converted twice: " + executable);
|
|
if (output.lexically_normal() == Io::NativePath(absPath).lexically_normal()) throw Domain::RelinkerException("Executable outputs collide: " + Io::Utf8Path(output));
|
|
for (const auto& extra : extraExecutables) {
|
|
if (std::filesystem::equivalent(extra.Input, input)) throw Domain::RelinkerException("Executable is converted twice: " + executable);
|
|
if (extra.Output.lexically_normal() == output.lexically_normal()) throw Domain::RelinkerException("Executable outputs collide: " + Io::Utf8Path(output));
|
|
}
|
|
extraExecutables.push_back({executable, relative, input, output});
|
|
}
|
|
|
|
const auto patches = LoadPatches(args);
|
|
const std::string mainTarget = Io::Utf8Path(Io::NativePath(args.inputPath).filename());
|
|
progress.IncludeFile(args.inputPath);
|
|
progress.IncludeFile(absPath);
|
|
for (const auto& extra : extraExecutables) {
|
|
progress.IncludeFile(Io::Utf8Path(extra.Input));
|
|
progress.IncludeFile(Io::Utf8Path(extra.Output));
|
|
}
|
|
std::vector<ConvertedExecutable> executables;
|
|
executables.push_back(Convert(args, args.inputPath, absPath, true, mainTarget, patches, progress));
|
|
for (const auto& [executable, relative, input, output] : extraExecutables) {
|
|
std::cout << "Executable: " << relative.generic_string() << '\n';
|
|
auto converted = Convert(args, Io::Utf8Path(input), Io::Utf8Path(output), false, relative.generic_string(), patches, progress);
|
|
const auto& mainArtifacts = executables.front().GuestArtifacts;
|
|
const bool shared = converted.GuestArtifacts.size() == mainArtifacts.size() && std::equal(mainArtifacts.begin(), mainArtifacts.end(), converted.GuestArtifacts.begin(), [](const auto& left, const auto& right) {
|
|
return left.Path == right.Path && left.Bytes == right.Bytes;
|
|
});
|
|
if (!shared) throw Domain::RelinkerException("Guest modules convert differently for " + executable + ", so it cannot share them with the input executable");
|
|
executables.push_back(std::move(converted));
|
|
}
|
|
const auto& guestArtifacts = executables.front().GuestArtifacts;
|
|
if (!patches.empty()) {
|
|
std::vector<std::string> targetNames{mainTarget};
|
|
for (const auto& extra : extraExecutables) targetNames.push_back(extra.Relative.generic_string());
|
|
if (!guestArtifacts.empty()) {
|
|
const auto patchModuleRoot = std::filesystem::absolute(Io::NativePath(args.sceModulePath)).lexically_normal();
|
|
for (const auto& artifact : guestArtifacts) targetNames.push_back(Relinker::GamePatch::ModuleTargetName(artifact.SourcePath, patchModuleRoot));
|
|
}
|
|
Relinker::GamePatch::MatchTargets(patches, targetNames);
|
|
}
|
|
std::vector<std::vector<Relinker::GamePatch::AppliedPatch>> appliedRecords;
|
|
for (const auto& executable : executables) appliedRecords.push_back(executable.AppliedPatches);
|
|
const auto appliedPatches = Relinker::GamePatch::AggregateManifest(appliedRecords);
|
|
const auto patchFile = args.patchesPath.empty() ? Io::NativePath(args.inputPath).parent_path() / Relinker::GamePatch::PatchFileName : Io::NativePath(args.patchesPath);
|
|
std::filesystem::path manifestPath;
|
|
std::string manifestText;
|
|
if (!appliedPatches.empty()) manifestPath = Io::NativePath(absPath).parent_path() / Io::NativePath(Io::Utf8Path(Io::NativePath(absPath).stem()) + Relinker::GamePatch::ManifestSuffix);
|
|
if (!manifestPath.empty()) manifestText = Relinker::GamePatch::WriteManifest(appliedPatches, mainTarget, Io::Utf8Path(patchFile));
|
|
|
|
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;
|
|
for (const auto& executable : executables)
|
|
if (SameOutputFile(Io::NativePath(executable.InputPath), path))
|
|
throw Domain::RelinkerException(std::string(kind) + " output would overwrite the input executable: " + Io::Utf8Path(path));
|
|
for (const auto& artifact : guestArtifacts)
|
|
if (SameOutputFile(artifact.SourcePath, path))
|
|
throw Domain::RelinkerException(std::string(kind) + " output would overwrite an input module: " + Io::Utf8Path(path));
|
|
};
|
|
for (std::size_t index = 1; index < executables.size(); ++index) checkOutputInputs(Io::NativePath(executables[index].OutputPath), "Executable");
|
|
if (desktopEntry) {
|
|
checkOutputInputs(desktopEntry->IconPath, "Icon");
|
|
checkOutputInputs(desktopEntry->EntryPath, "Desktop entry");
|
|
}
|
|
if (args.windowsDiagnostics)
|
|
checkOutputInputs(std::filesystem::absolute("windows-diagnostics-imports.txt"), "Windows import diagnostics");
|
|
|
|
std::vector<std::filesystem::path> registryPaths;
|
|
std::vector<std::filesystem::path> neededPaths;
|
|
if (args.writeRegistry) {
|
|
for (const auto& executable : executables) {
|
|
const std::filesystem::path outFsPath = Io::NativePath(executable.OutputPath);
|
|
registryPaths.push_back(outFsPath.parent_path() / Io::NativePath(Io::Utf8Path(outFsPath.stem()) + ".registry.json"));
|
|
}
|
|
const std::filesystem::path outFsPath = Io::NativePath(absPath);
|
|
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) checkOutputInputs(path, "Registry");
|
|
for (const auto& path : registryPaths) {
|
|
auto name = Io::Utf8Path(path.filename());
|
|
name.resize(name.size() - std::string(".registry.json").size());
|
|
neededPaths.push_back(path.parent_path() / Io::NativePath(name + ".needed.json"));
|
|
}
|
|
for (const auto& path : neededPaths) checkOutputInputs(path, "Needed libraries");
|
|
}
|
|
|
|
checkOutputInputs(Io::NativePath(absPath), "Executable");
|
|
if (!manifestPath.empty()) {
|
|
checkOutputInputs(manifestPath, "Patch manifest");
|
|
if (std::filesystem::exists(manifestPath) && std::filesystem::exists(patchFile) && std::filesystem::equivalent(manifestPath, patchFile))
|
|
throw Domain::RelinkerException("Patch manifest output would overwrite the patch file: " + Io::Utf8Path(manifestPath));
|
|
}
|
|
for (const auto& artifact : guestArtifacts) checkOutputInputs(artifact.Path, "Guest module");
|
|
|
|
std::vector<std::filesystem::path> outputPaths;
|
|
for (const auto& executable : executables) outputPaths.push_back(Io::NativePath(executable.OutputPath));
|
|
for (const auto& artifact : guestArtifacts) outputPaths.push_back(artifact.Path);
|
|
outputPaths.insert(outputPaths.end(), registryPaths.begin(), registryPaths.end());
|
|
outputPaths.insert(outputPaths.end(), neededPaths.begin(), neededPaths.end());
|
|
if (!manifestPath.empty()) outputPaths.push_back(manifestPath);
|
|
if (desktopEntry) {
|
|
outputPaths.push_back(desktopEntry->IconPath);
|
|
outputPaths.push_back(desktopEntry->EntryPath);
|
|
}
|
|
if (args.windowsDiagnostics) outputPaths.push_back(std::filesystem::absolute("windows-diagnostics-imports.txt"));
|
|
for (std::size_t index = 0; index < outputPaths.size(); ++index) {
|
|
for (std::size_t previous = 0; previous < index; ++previous) {
|
|
const auto& first = outputPaths[index];
|
|
const auto& second = outputPaths[previous];
|
|
if (SameOutputDestination(first, second))
|
|
throw Domain::RelinkerException("Output paths collide: " + Io::Utf8Path(first) + " and " + Io::Utf8Path(second));
|
|
}
|
|
}
|
|
|
|
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});
|
|
}
|
|
for (const auto& executable : executables) {
|
|
fileWriter.Write(executable.OutputPath, executable.Bytes);
|
|
if (!args.toWindows) {
|
|
std::error_code error;
|
|
std::filesystem::permissions(Io::NativePath(executable.OutputPath), std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec, std::filesystem::perm_options::add, error);
|
|
if (error) throw Domain::RelinkerException("Cannot make the output executable: " + executable.OutputPath + " (" + error.message() + ")");
|
|
}
|
|
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) {
|
|
fileWriter.Write(Io::Utf8Path(registryPaths[index]), registryWriter->WriteCallRegistry(executables[index].Result.RegistryEntries));
|
|
fileWriter.Write(Io::Utf8Path(neededPaths[index]), registryWriter->WriteNeededLibraries(executables[index].Result.DynamicSection.NeededLibraries));
|
|
}
|
|
for (std::size_t index = 0; index < guestArtifacts.size(); ++index) {
|
|
fileWriter.Write(Io::Utf8Path(registryPaths[executables.size() + index]), registryWriter->WriteModuleImports(guestArtifacts[index].Imports));
|
|
fileWriter.Write(Io::Utf8Path(neededPaths[executables.size() + index]), registryWriter->WriteNeededLibraries(guestArtifacts[index].NeededLibraries));
|
|
}
|
|
}
|
|
if (!manifestPath.empty()) fileWriter.Write(Io::Utf8Path(manifestPath), manifestText);
|
|
progress.Finish();
|
|
if (!manifestPath.empty()) std::cout << "Patch manifest: " << Io::Utf8Path(manifestPath) << '\n';
|
|
if (desktopEntry) std::cout << "Desktop entry: " << Io::Utf8Path(desktopEntryWriter.Write(*desktopEntry)) << '\n';
|
|
std::cout << "\nConversion complete\n"
|
|
<< "Output file: " << absPath << '\n'
|
|
<< "External prx references: " << executables.front().Result.RegistryEntries.size() << '\n'
|
|
<< "Converted modules: " << guestArtifacts.size() << "\n\n";
|
|
std::vector<std::string> extraPaths;
|
|
for (std::size_t index = 1; index < executables.size(); ++index) {
|
|
std::cout << "Executable file: " << executables[index].OutputPath << '\n';
|
|
extraPaths.push_back(Io::Utf8Path(Io::NativePath(executables[index].OutputPath).filename()));
|
|
}
|
|
std::vector<std::string> modulePaths;
|
|
for (const auto& artifact : guestArtifacts) {
|
|
std::string relative = Io::Utf8Path(artifact.Path.lexically_relative(Io::NativePath(absPath).parent_path().lexically_normal() / "app0"));
|
|
std::replace(relative.begin(), relative.end(), '\\', '/');
|
|
modulePaths.push_back(std::move(relative));
|
|
}
|
|
Cli::RuntimeLayout::Print(std::cout, Io::Utf8Path(Io::NativePath(absPath).filename()), modulePaths, extraPaths);
|
|
if (args.runPath != "$ORIGIN/libs") std::cout << "Custom library search path (--rpath): " << args.runPath << '\n';
|
|
|
|
if (args.autorun) return Cli::Autorun(absPath, args.toWindows);
|
|
|
|
} catch (const Domain::RelinkerException& e) {
|
|
progress.Finish();
|
|
std::cerr << "FAIL: " << e.what();
|
|
if (e.FailureOffset != 0) std::cerr << " (offset 0x" << std::hex << e.FailureOffset << ")";
|
|
std::cerr << "\n";
|
|
if (!e.InputPath.empty()) std::cerr << "Input: " << e.InputPath << '\n';
|
|
return 2;
|
|
} catch (const Codegen::CodegenException& e) {
|
|
progress.Finish();
|
|
std::cerr << "FAIL: " << e.what();
|
|
if (e.FailureOffset != 0) std::cerr << " (offset 0x" << std::hex << e.FailureOffset << ")";
|
|
std::cerr << "\n";
|
|
return 2;
|
|
} catch (const std::exception& e) {
|
|
progress.Finish();
|
|
std::cerr << "FAIL: " << e.what() << "\n";
|
|
return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
std::string Utf8Argument(const wchar_t* argument) {
|
|
const std::size_t argumentSize = std::wcslen(argument);
|
|
if (argumentSize == 0) return {};
|
|
const int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, argument, static_cast<int>(argumentSize), nullptr, 0, nullptr, nullptr);
|
|
if (size <= 0) throw std::runtime_error("Cannot encode the command line as UTF-8");
|
|
std::string utf8(static_cast<std::size_t>(size), '\0');
|
|
if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, argument, static_cast<int>(argumentSize), utf8.data(), size, nullptr, nullptr) != size)
|
|
throw std::runtime_error("Cannot encode the command line as UTF-8");
|
|
return utf8;
|
|
}
|
|
|
|
}
|
|
|
|
int wmain(const int argc, wchar_t* argv[]) {
|
|
try {
|
|
std::vector<std::string> arguments;
|
|
arguments.reserve(static_cast<std::size_t>(argc));
|
|
arguments.emplace_back();
|
|
for (int index = 1; index < argc; ++index) arguments.push_back(Utf8Argument(argv[index]));
|
|
std::vector<char*> pointers;
|
|
pointers.reserve(arguments.size() + 1);
|
|
for (auto& argument : arguments) pointers.push_back(argument.data());
|
|
pointers.push_back(nullptr);
|
|
return Run(static_cast<int>(arguments.size()), pointers.data());
|
|
} catch (const std::exception& error) {
|
|
std::cerr << "FAIL: " << error.what() << "\n";
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
}
|
|
|
|
int main(const int argc, char* argv[]) {
|
|
return Run(argc, argv);
|
|
}
|
|
|
|
#endif
|