Merge pull request #50 from techs-sus/main

Add Nix packaging
This commit is contained in:
Savva authored and GitHub committed 2026-08-24 16:16:24 +03:00
commit 2f179068b3
5 files changed
+301 -6

No files matched your search

Generated
+61
View File
@@ -0,0 +1,61 @@
{
"nodes": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1785627969,
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1787135253,
"narHash": "sha256-RD2kNWCG+Bjo6h+JVjWVNntZs2GtRoeY2xHjts/FNkA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1785031560,
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+31
View File
@@ -0,0 +1,31 @@
{
description = "Description for the project";
inputs = {
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
];
systems = [
"x86_64-linux"
# "aarch64-linux"
# "aarch64-darwin"
# "x86_64-darwin"
];
perSystem =
{
pkgs,
...
}:
{
packages.default = pkgs.callPackage ./packaging/nix/package.nix { };
};
flake = { };
};
}
+136
View File
@@ -0,0 +1,136 @@
{
lib,
stdenv,
fetchFromGitHub,
cmake,
git,
lld,
ninja,
pkg-config,
vulkan-headers,
makeWrapper,
capstone,
curl,
fontconfig,
glib,
gtk4,
hicolor-icon-theme,
libadwaita,
elfutils,
libglvnd,
libplacebo,
libsoup_3,
libutf8proc,
libyaml,
minizip,
nlohmann_json,
openssl,
sdl3,
sdl3-ttf,
vulkan-loader,
webkitgtk_6_0,
zlib,
}:
let
# submodules are not part of the flake source tree
# so these two vendored dependencies are fetched from their pinned upstream revisions
libjnivmSrc = fetchFromGitHub {
owner = "ChristopherHX";
repo = "libjnivm";
rev = "f24b98c198fcc5c59a68d1efadd3f5791eb01c2e";
hash = "sha256-K4+1Zo7AJIouIqnHn2ZE4GyrNrYOczmWYblLAnGYA5w=";
};
vulkanHeadersSrc = fetchFromGitHub {
owner = "KhronosGroup";
repo = "Vulkan-Headers";
rev = "8d6039a455a7ecc7d2a592ff97f62db4e59b70bf";
hash = "sha256-+llJlEdzFcmqGBAdKHjYhkwBuH19ZngavmF1TQgejsI=";
};
src = ../..;
lines = lib.splitString "\n" (builtins.readFile (lib.path.append src "CMakeLists.txt"));
matches = map (builtins.match "[[:space:]]*VERSION[[:space:]]+([0-9][0-9.]*).*") lines;
match = lib.findFirst (m: m != null) null matches;
version =
if match == null then throw "could not find VERSION in CMakeLists.txt" else builtins.elemAt match 0;
in
stdenv.mkDerivation (finalAttrs: {
pname = "mocktail";
inherit version;
inherit src;
nativeBuildInputs = [
cmake
git
lld
ninja
pkg-config
vulkan-headers
makeWrapper
];
buildInputs = [
capstone
curl
fontconfig
glib
gtk4
hicolor-icon-theme
libadwaita
elfutils
libglvnd
libplacebo
libsoup_3
libutf8proc
libyaml
minizip
nlohmann_json
openssl
sdl3
sdl3-ttf
vulkan-loader
webkitgtk_6_0
zlib
];
preConfigure = ''
rm -rf third_party/libjnivm third_party/Vulkan-Headers
cp -r --no-preserve=mode ${libjnivmSrc} third_party/libjnivm
cp -r --no-preserve=mode ${vulkanHeadersSrc} third_party/Vulkan-Headers
'';
# nixpkgs installs the header we need under include/libutf8proc
NIX_CFLAGS_COMPILE = "-I${libutf8proc}/include/libutf8proc";
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_INSTALL_LIBDIR=lib"
"-DMOCKTAIL_DEFAULT_COMPATIBILITY_MANIFEST=${placeholder "out"}/share/mocktail/metadata/roblox_compatibility.json"
"-DMOCKTAIL_DEFAULT_SIGNING_TRUST_MANIFEST=${placeholder "out"}/share/mocktail/metadata/roblox_signing_certificates.json"
"-DBUILD_TESTING=OFF"
];
postInstall = ''
for binary in $out/bin/*; do
wrapProgram "$binary" --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath finalAttrs.buildInputs}
done
'';
fixupPhase = ''
install -Dm644 ${finalAttrs.src}/LICENSE "$out/share/licenses/${finalAttrs.pname}/LICENSE"
'';
meta = with lib; {
description = "Android x86-64 Roblox compatibility runtime for Linux";
homepage = "https://github.com/komaruworld/mocktail";
license = licenses.asl20;
platforms = platforms.unix;
mainProgram = "mocktail";
};
})
+65 -6
View File
@@ -7,6 +7,7 @@
#include <cstdlib>
#include <cstring>
#include <dlfcn.h>
#include <link.h>
#include <iomanip>
#include <iostream>
#include <csignal>
@@ -2651,6 +2652,29 @@ struct SymbolResolveResult {
SymbolResolveSource source = SymbolResolveSource::kMissing;
};
// dlsym searches the object and its dependency chain, so a
// stub that transitively links SDL or another host library would
// satisfy EGL/GL/Vulkan lookups with unrelated host symbols
//
// So, we should only accept the symbol if its stub owns it.
bool StubOwnsSymbolAddress(void* handle, void* address) {
if (handle == nullptr || address == nullptr) {
return false;
}
Dl_info info = {};
if (::dladdr(address, &info) == 0 || info.dli_fbase == nullptr) {
return false;
}
link_map* map = nullptr;
if (::dlinfo(handle, RTLD_DI_LINKMAP, &map) != 0 || map == nullptr) {
return false;
}
return reinterpret_cast<void*>(map->l_addr) == info.dli_fbase;
}
SymbolResolveResult ResolveSymbolForBionic(const char* name, bool has_window,
void* real_gles_handle,
const std::vector<void*>& stub_handles) {
@@ -2677,8 +2701,8 @@ SymbolResolveResult ResolveSymbolForBionic(const char* name, bool has_window,
}
for (void* h : stub_handles) {
result.address = ::dlsym(h, name);
if (result.address != nullptr) {
if (void *candidate = ::dlsym(h, name); candidate != nullptr && StubOwnsSymbolAddress(h, candidate)) {
result.address = candidate;
result.source = SymbolResolveSource::kStub;
return result;
}
@@ -30398,6 +30422,32 @@ void* DelayedSendGameLoadedThread(void* arg) {
return nullptr;
}
// Resolve the exact Android Vulkan loader adapter shipped next to this
// runtime binary. Never resolve it by SONAME: an unversioned host
// libvulkan.so on the search path (for example distribution or Nix wrappers)
// would win, silently drop VK_KHR_android_surface support, and break
// direct-Vulkan mode with "Unable to create Vulkan instance".
static std::string RuntimeVulkanAdapterPath() {
const char* override_dir = std::getenv("MOCKTAIL_RUNTIME_LIBRARY_DIR");
std::string directory;
if (override_dir != nullptr && override_dir[0] != '\0') {
directory = override_dir;
} else {
char executable_path[4097] = {};
const ssize_t length =
::readlink("/proc/self/exe", executable_path,
sizeof(executable_path) - 1U);
if (length <= 0) return {};
executable_path[length] = '\0';
std::string path(executable_path);
const std::string::size_type separator = path.find_last_of('/');
if (separator == std::string::npos) return {};
directory = separator == 0 ? "/" : path.substr(0, separator);
}
if (directory.empty()) return {};
return directory + "/libvulkan.so";
}
void* EngineStartupThread(void* arg) {
auto* context = static_cast<EngineStartupContext*>(arg);
if (context == nullptr) {
@@ -32267,17 +32317,25 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options,
void* bionic_vulkan_adapter_handle = nullptr;
for (const char* name : stub_names) {
void* h = nullptr;
bool exact_adapter = false;
if (std::strcmp(name, "libEGL.so") == 0 &&
bionic_egl_bridge.IsLoaded()) {
h = bionic_egl_bridge.handle();
} else {
exact_adapter = true;
} else if (std::strcmp(name, "libvulkan.so") == 0) {
const std::string vulkan_adapter = RuntimeVulkanAdapterPath();
if (!vulkan_adapter.empty()) {
h = ::dlopen(vulkan_adapter.c_str(), RTLD_LAZY | RTLD_GLOBAL);
exact_adapter = h != nullptr;
}
}
if (h == nullptr) {
h = ::dlopen(name, RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
if (!h) h = ::dlopen(name, RTLD_LAZY | RTLD_GLOBAL);
}
if (h) {
std::cout << " [stubs] Preloaded " << name;
if (std::strcmp(name, "libEGL.so") == 0 &&
bionic_egl_bridge.IsLoaded()) {
if (exact_adapter) {
std::cout << " via exact Mocktail adapter";
}
std::cout << '\n';
@@ -32806,7 +32864,8 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options,
size_t vulkan_exports = 0;
for (const char* name : kVulkanAdapterExports) {
void* address = ::dlsym(bionic_vulkan_adapter_handle, name);
if (address == nullptr) {
if (address == nullptr ||
!StubOwnsSymbolAddress(bionic_vulkan_adapter_handle, address)) {
continue;
}
linker::RegisterSyntheticSymbol("libvulkan.so", name, address);
+8
View File
@@ -318,6 +318,14 @@ CanaryResult RunReadinessCanary(const CanaryOptions& options) {
"__NV_PRIME_RENDER_OFFLOAD",
"__VK_LAYER_NV_optimus",
"__GLX_VENDOR_LIBRARY_NAME",
// Dynamic-library search state and graphics library overrides
// must reach the isolated runtime so that distributions which
// resolve shared libraries through the launcher environment
// (for example NixOS wrappers) still find libGLESv2/libEGL.
"LD_LIBRARY_PATH",
"MOCKTAIL_EGL_LIBRARY",
"MOCKTAIL_GLES_LIBRARY",
"MOCKTAIL_ANGLE_LIB_DIR",
}) {
AddInherited(name, &environment);
}