AnyPS5/core/relinker/wasm/Bindings.cpp

100 lines
4.7 KiB
C++

#include <relinker/pipeline/Conversion.hpp>
#include <io/NativePath.hpp>
#include <emscripten/bind.h>
#include <stdexcept>
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<unsigned>(event.Stage));
report.set("input", event.Input);
report.set("completed", static_cast<double>(event.Completed));
report.set("total", static_cast<double>(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<std::uint8_t> 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<std::size_t>();
if (length > remaining) throw std::invalid_argument("Executable, modules and icon exceed the 256 MiB conversion limit");
remaining -= length;
std::vector<std::uint8_t> result(length);
if (length != 0) emscripten::val(emscripten::typed_memory_view(length, result.data())).call<void>("set", bytes);
return result;
}
emscripten::val CopyBytes(const std::vector<std::uint8_t>& 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<std::string>();
if (target != "windows" && target != "linux") throw std::invalid_argument("Unknown target system");
const auto name = request["name"].as<std::string>();
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<std::string>());
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<unsigned>();
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<std::string>());
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<bool>();
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<void>("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);
}