#include #include #include #include namespace { class BrowserProgress final : public Domain::IProgress { public: explicit BrowserProgress(emscripten::val callback) : callback(std::move(callback)) {} void Report(const Domain::ProgressEvent& event) override { auto report = emscripten::val::object(); report.set("stage", static_cast(event.Stage)); report.set("input", event.Input); report.set("completed", static_cast(event.Completed)); report.set("total", static_cast(event.Total)); callback(report); } void Message(std::string_view message) override { auto report = emscripten::val::object(); report.set("message", std::string(message)); callback(report); } private: emscripten::val callback; }; std::vector ReadBytes(const emscripten::val& bytes, std::size_t& remaining) { if (!bytes.instanceof(emscripten::val::global("Uint8Array"))) throw std::invalid_argument("Expected Uint8Array input"); const auto length = bytes["length"].as(); if (length > remaining) throw std::invalid_argument("Executable, modules and icon exceed the 256 MiB conversion limit"); remaining -= length; std::vector result(length); if (length != 0) emscripten::val(emscripten::typed_memory_view(length, result.data())).call("set", bytes); return result; } emscripten::val CopyBytes(const std::vector& bytes) { return emscripten::val::global("Uint8Array").new_(emscripten::val(emscripten::typed_memory_view(bytes.size(), bytes.data()))); } emscripten::val Convert(const emscripten::val& request, const emscripten::val& callback) { auto result = emscripten::val::object(); try { const auto target = request["target"].as(); if (target != "windows" && target != "linux") throw std::invalid_argument("Unknown target system"); const auto name = request["name"].as(); if (name.empty() || name.find_first_of("/\\") != std::string::npos || name == "." || name == "..") throw std::invalid_argument("Invalid executable name"); std::size_t remaining = 256 * 1024 * 1024; Relinker::ConversionInput input; input.InputPath = Io::NativePath("/input/" + name); input.OutputPath = Io::NativePath("/output/" + request["outputName"].as()); if (input.OutputPath.parent_path() != "/output") throw std::invalid_argument("Invalid output name"); input.ModuleRoot = "/input"; input.Executable = ReadBytes(request["executable"], remaining); const auto modules = request["modules"]; const auto count = modules["length"].as(); if (count > 4096) throw std::invalid_argument("Too many modules"); for (unsigned index = 0; index < count; ++index) { const auto module = modules[index]; const auto path = Io::NativePath(module["name"].as()); if (path.is_absolute() || path.lexically_normal() != path || path.begin() == path.end() || *path.begin() != "sce_module") throw std::invalid_argument("Invalid module path"); input.Modules.push_back({Io::NativePath("/input") / path, ReadBytes(module["bytes"], remaining)}); } input.RuntimeDirectories.push_back("/input/sce_module"); if (!request["icon"].isNull()) input.Icon = ReadBytes(request["icon"], remaining); Relinker::ConversionOptions options; options.Target = target == "windows" ? Relinker::TargetSystem::Windows : Relinker::TargetSystem::Linux; options.WindowsGui = false; options.WindowsDiagnostics = target == "windows"; options.ToIntel = request["toIntel"].as(); BrowserProgress progress(callback); auto output = Relinker::Conversion(progress).Convert(std::move(input), options); result.set("executable", CopyBytes(output.Executable)); result.set("diagnostics", output.WindowsDiagnostics); auto artifacts = emscripten::val::array(); for (const auto& module : output.Modules) { auto artifact = emscripten::val::object(); artifact.set("name", Io::Utf8Path(module.Path.lexically_relative("/output"))); artifact.set("bytes", CopyBytes(module.Bytes)); artifacts.call("push", artifact); } result.set("modules", artifacts); result.set("ok", true); } catch (const std::exception& error) { result.set("ok", false); result.set("error", std::string(error.what())); } return result; } } EMSCRIPTEN_BINDINGS(relinker) { emscripten::function("Convert", &Convert); }