Files
ObsidianDragon/src/resources/embedded_resources.cpp
DanS b2e104358d feat(fullnode): manage the daemon binary in Settings; stop auto-overwriting it
Previously the wallet re-extracted the bundled dragonxd on startup whenever the
installed binary's size differed from the bundle ("stale" overwrite), which could
replace a node a user had deliberately placed in dragonx/.

Now dragonx binaries (dragonxd/cli/tx) are auto-placed ONLY when missing — never
auto-overwritten on a size mismatch (needsParamsExtraction + extractEmbeddedResources).
Params/asmap keep their size-based refresh; a daemon dropped next to the wallet exe
still takes priority and is never touched.

Replacing the daemon is now an explicit action: Settings → "Daemon binary" reports the
installed binary's version (scanned from the file), size and modified date, compares it
to the version bundled in this build, and offers an "Install bundled daemon" button.
That stops the node, overwrites dragonxd/cli/tx with the bundled copies (waiting for the
process to release the file lock), and restarts — wallet/keys/chain data untouched.

Adds resources::{getInstalledDaemonInfo,getBundledDaemonInfo,reextractBundledDaemon}
(+ a version-string scanner) and App::reinstallBundledDaemon().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:06:59 -05:00

781 lines
27 KiB
C++

#include "embedded_resources.h"
#include "../util/platform.h"
#include "../util/logger.h"
#include <fstream>
#include <filesystem>
#include <vector>
#include <cstdio>
#include <cctype>
#include <chrono>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif
// Include generated resource data if available
#if __has_include("embedded_data.h")
#include "embedded_data.h"
#define HAS_EMBEDDED_RESOURCES 1
#else
#define HAS_EMBEDDED_RESOURCES 0
#endif
namespace dragonx {
namespace resources {
#if HAS_EMBEDDED_RESOURCES
// Resource table - populated by embedded_data.h (INCBIN symbols: g_NAME_data / g_NAME_size)
static const EmbeddedResource s_resources[] = {
{ g_sapling_spend_params_data, g_sapling_spend_params_size, RESOURCE_SAPLING_SPEND },
{ g_sapling_output_params_data, g_sapling_output_params_size, RESOURCE_SAPLING_OUTPUT },
{ g_asmap_dat_data, g_asmap_dat_size, RESOURCE_ASMAP },
#ifdef HAS_EMBEDDED_DAEMON
{ g_dragonxd_exe_data, g_dragonxd_exe_size, RESOURCE_DRAGONXD },
{ g_dragonx_cli_exe_data, g_dragonx_cli_exe_size, RESOURCE_DRAGONX_CLI },
{ g_dragonx_tx_exe_data, g_dragonx_tx_exe_size, RESOURCE_DRAGONX_TX },
#endif
#ifdef HAS_EMBEDDED_XMRIG
{ g_xmrig_exe_data, g_xmrig_exe_size, RESOURCE_XMRIG },
#endif
#ifdef HAS_EMBEDDED_GRADIENT
{ g_dark_gradient_png_data, g_dark_gradient_png_size, RESOURCE_DARK_GRADIENT },
#endif
#ifdef HAS_EMBEDDED_LOGO
{ g_logo_ObsidianDragon_dark_png_data, g_logo_ObsidianDragon_dark_png_size, RESOURCE_LOGO },
#endif
{ nullptr, 0, nullptr } // Sentinel
};
// Embedded themes table is generated by s_embedded_themes[] in embedded_data.h
#else
static const EmbeddedResource s_resources[] = {
{ nullptr, 0, nullptr } // No embedded resources
};
// No embedded themes on non-Windows builds (themes live on disk)
struct EmbeddedThemeEntry { const uint8_t* data; unsigned int size; const char* filename; };
static const EmbeddedThemeEntry s_embedded_themes[] = {
{ nullptr, 0, nullptr }
};
// No embedded images on non-Windows builds (images live on disk)
struct EmbeddedImageEntry { const uint8_t* data; unsigned int size; const char* filename; };
static const EmbeddedImageEntry s_embedded_images[] = {
{ nullptr, 0, nullptr }
};
#endif
bool hasEmbeddedResources()
{
#if HAS_EMBEDDED_RESOURCES
return true;
#else
return false;
#endif
}
const EmbeddedResource* getEmbeddedResource(const std::string& name)
{
// Search static resource table (params, daemon binaries, etc.)
for (const auto* res = s_resources; res->data != nullptr; ++res) {
if (name == res->filename) {
return res;
}
}
// Search dynamically generated image table (backgrounds + logos)
// These are generated by build.sh from res/img/ contents.
for (const auto* img = s_embedded_images; img->data != nullptr; ++img) {
if (name == img->filename) {
static thread_local EmbeddedResource imageResult;
imageResult = { img->data, img->size, img->filename };
return &imageResult;
}
}
return nullptr;
}
const EmbeddedTheme* getEmbeddedThemes()
{
// Map from generated table to public struct
// s_embedded_themes is generated by build.sh (or empty fallback)
static std::vector<EmbeddedTheme> themes;
static bool init = false;
if (!init) {
for (const auto* t = s_embedded_themes; t->data != nullptr; ++t) {
themes.push_back({ t->data, t->size, t->filename });
}
themes.push_back({ nullptr, 0, nullptr }); // Sentinel
init = true;
}
return themes.data();
}
int extractBundledThemes(const std::string& destDir)
{
namespace fs = std::filesystem;
int count = 0;
const auto* themes = getEmbeddedThemes();
if (!themes || !themes->data) return 0;
// Ensure destination exists
std::error_code ec;
fs::create_directories(destDir, ec);
if (ec) {
DEBUG_LOGF("[ERROR] EmbeddedResources: Failed to create theme dir: %s\n", destDir.c_str());
return 0;
}
for (const auto* t = themes; t->data != nullptr; ++t) {
fs::path dest = fs::path(destDir) / t->filename;
// Always overwrite — bundled themes should match the binary version
std::ofstream f(dest, std::ios::binary);
if (f.is_open()) {
f.write(reinterpret_cast<const char*>(t->data), t->size);
f.close();
DEBUG_LOGF("[INFO] EmbeddedResources: Extracted theme: %s (%zu bytes)\n",
t->filename, t->size);
count++;
} else {
DEBUG_LOGF("[ERROR] EmbeddedResources: Failed to write theme: %s\n", dest.string().c_str());
}
}
return count;
}
int updateBundledThemes(const std::string& dir)
{
namespace fs = std::filesystem;
int count = 0;
const auto* themes = getEmbeddedThemes();
if (!themes || !themes->data) return 0;
if (!fs::exists(dir)) return 0;
for (const auto* t = themes; t->data != nullptr; ++t) {
fs::path dest = fs::path(dir) / t->filename;
if (!fs::exists(dest)) {
// New theme not yet on disk — extract it
} else {
std::error_code ec;
auto diskSize = fs::file_size(dest, ec);
if (!ec && diskSize == static_cast<std::uintmax_t>(t->size))
continue; // up to date
}
std::ofstream f(dest, std::ios::binary);
if (f.is_open()) {
f.write(reinterpret_cast<const char*>(t->data), t->size);
f.close();
DEBUG_LOGF("[INFO] EmbeddedResources: Updated stale theme: %s (%zu bytes)\n",
t->filename, t->size);
count++;
}
}
return count;
}
std::string getParamsDirectory()
{
#ifdef _WIN32
char appdata[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, appdata))) {
return std::string(appdata) + "\\ZcashParams";
}
return "";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw ? pw->pw_dir : "/tmp";
}
return std::string(home) + "/Library/Application Support/ZcashParams";
#else
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
home = pw ? pw->pw_dir : "/tmp";
}
return std::string(home) + "/.zcash-params";
#endif
}
// Forward declaration — defined below extractResource()
static bool resourceNeedsUpdate(const EmbeddedResource* res, const std::string& destPath);
bool needsParamsExtraction()
{
if (!hasEmbeddedResources()) {
return false;
}
// Check daemon directory (dragonx/) — the only extraction target
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
const char pathSep = '\\';
#else
const char pathSep = '/';
#endif
std::string spendPath = daemonDir + pathSep + RESOURCE_SAPLING_SPEND;
std::string outputPath = daemonDir + pathSep + RESOURCE_SAPLING_OUTPUT;
// Check if params are missing or stale (size mismatch → updated in newer build)
const auto* spendRes = getEmbeddedResource(RESOURCE_SAPLING_SPEND);
const auto* outputRes = getEmbeddedResource(RESOURCE_SAPLING_OUTPUT);
if (spendRes && resourceNeedsUpdate(spendRes, spendPath)) return true;
if (outputRes && resourceNeedsUpdate(outputRes, outputPath)) return true;
// Daemon binaries are only auto-placed when MISSING (never auto-overwritten on a size
// mismatch) — the user may be running a specific dragonxd. Replacing the bundled daemon is
// an explicit action via Settings → daemon binary. So only trigger extraction if it's absent.
#ifdef HAS_EMBEDDED_DAEMON
const auto* daemonRes = getEmbeddedResource(RESOURCE_DRAGONXD);
std::string daemonPath = daemonDir + pathSep + RESOURCE_DRAGONXD;
if (daemonRes && !std::filesystem::exists(daemonPath)) return true;
#endif
#ifdef HAS_EMBEDDED_XMRIG
const auto* xmrigRes = getEmbeddedResource(RESOURCE_XMRIG);
std::string xmrigPath = daemonDir + pathSep + RESOURCE_XMRIG;
if (xmrigRes && resourceNeedsUpdate(xmrigRes, xmrigPath)) return true;
#endif
return false;
}
// Check if an on-disk file is missing or differs in size from the embedded resource.
// A size mismatch means the binary was updated in a newer wallet build.
static bool resourceNeedsUpdate(const EmbeddedResource* res, const std::string& destPath)
{
if (!res || !res->data || res->size == 0) return false;
if (!std::filesystem::exists(destPath)) return true;
std::error_code ec;
auto diskSize = std::filesystem::file_size(destPath, ec);
if (ec) return true; // can't stat → re-extract
return diskSize != static_cast<std::uintmax_t>(res->size);
}
static bool extractResource(const EmbeddedResource* res, const std::string& destPath)
{
if (!res || !res->data || res->size == 0) {
return false;
}
// Create parent directories
std::filesystem::path path(destPath);
if (path.has_parent_path()) {
std::error_code ec;
std::filesystem::create_directories(path.parent_path(), ec);
if (ec) {
DEBUG_LOGF("[ERROR] Failed to create directory %s: %s\n",
path.parent_path().string().c_str(), ec.message().c_str());
return false;
}
}
// Write file
std::ofstream file(destPath, std::ios::binary);
if (!file) {
// The destination may be locked because the previous daemon is still using the binary:
// Windows locks a running .exe against truncation, Linux returns ETXTBSY. Both platforms
// DO allow renaming/moving such a file — the running process keeps the moved copy — so move
// the stale binary aside and write a fresh one at the original path.
std::error_code ec;
if (std::filesystem::exists(destPath)) {
std::string sidelined = destPath + ".old";
std::filesystem::remove(sidelined, ec); // clear any leftover from a prior swap
ec.clear();
std::filesystem::rename(destPath, sidelined, ec);
if (!ec) {
file.clear();
file.open(destPath, std::ios::binary);
if (file)
DEBUG_LOGF("[INFO] Replaced in-use %s (old copy moved to .old)\n", destPath.c_str());
} else {
DEBUG_LOGF("[WARN] Could not move stale %s aside: %s\n",
destPath.c_str(), ec.message().c_str());
}
}
if (!file) {
DEBUG_LOGF("[ERROR] Failed to open %s for writing\n", destPath.c_str());
return false;
}
}
file.write(reinterpret_cast<const char*>(res->data), res->size);
if (!file) {
DEBUG_LOGF("[ERROR] Failed to write %zu bytes to %s\n", res->size, destPath.c_str());
return false;
}
DEBUG_LOGF("[INFO] Extracted %s (%zu bytes)\n", destPath.c_str(), res->size);
return true;
}
bool extractEmbeddedResources()
{
if (!hasEmbeddedResources()) {
DEBUG_LOGF("[ERROR] No embedded resources available\n");
return false;
}
bool success = true;
#ifdef _WIN32
const char pathSep = '\\';
#else
const char pathSep = '/';
#endif
// All files go to <ObsidianDragonDir>/dragonx/
std::string daemonDir = getDaemonDirectory();
// Extract Sapling params to daemon directory alongside dragonxd
const EmbeddedResource* spendRes = getEmbeddedResource(RESOURCE_SAPLING_SPEND);
if (spendRes) {
std::string dest = daemonDir + pathSep + RESOURCE_SAPLING_SPEND;
if (resourceNeedsUpdate(spendRes, dest)) {
DEBUG_LOGF("[INFO] Extracting sapling-spend.params (%zu MB)...\n", spendRes->size / (1024*1024));
if (!extractResource(spendRes, dest)) {
success = false;
}
}
}
const EmbeddedResource* outputRes = getEmbeddedResource(RESOURCE_SAPLING_OUTPUT);
if (outputRes) {
std::string dest = daemonDir + pathSep + RESOURCE_SAPLING_OUTPUT;
if (resourceNeedsUpdate(outputRes, dest)) {
DEBUG_LOGF("[INFO] Extracting sapling-output.params (%zu MB)...\n", outputRes->size / (1024*1024));
if (!extractResource(outputRes, dest)) {
success = false;
}
}
}
// Extract asmap.dat to daemon directory
const EmbeddedResource* asmapRes = getEmbeddedResource(RESOURCE_ASMAP);
if (asmapRes) {
std::string dest = daemonDir + pathSep + RESOURCE_ASMAP;
if (resourceNeedsUpdate(asmapRes, dest)) {
DEBUG_LOGF("[INFO] Extracting asmap.dat...\n");
if (!extractResource(asmapRes, dest)) {
success = false;
}
}
}
// Extract daemon binaries — NOT the data directory.
// Running dragonxd.exe from inside the data directory (where it writes blockchain
// data, debug.log, lock files, etc.) causes crashes on some Windows machines.
#ifdef HAS_EMBEDDED_DAEMON
DEBUG_LOGF("[INFO] Daemon extraction directory: %s\n", daemonDir.c_str());
// Daemon binaries are placed ONLY when missing — never auto-overwritten on a size mismatch
// (the user may run a specific dragonxd; replacing it is an explicit Settings action).
const EmbeddedResource* daemonRes = getEmbeddedResource(RESOURCE_DRAGONXD);
if (daemonRes) {
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONXD;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting dragonxd (%zu MB)...\n", daemonRes->size / (1024*1024));
if (!extractResource(daemonRes, dest)) {
success = false;
}
#ifndef _WIN32
else { chmod(dest.c_str(), 0755); }
#endif
}
}
const EmbeddedResource* cliRes = getEmbeddedResource(RESOURCE_DRAGONX_CLI);
if (cliRes) {
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_CLI;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting dragonx-cli (%zu MB)...\n", cliRes->size / (1024*1024));
if (!extractResource(cliRes, dest)) {
success = false;
}
#ifndef _WIN32
else { chmod(dest.c_str(), 0755); }
#endif
}
}
const EmbeddedResource* txRes = getEmbeddedResource(RESOURCE_DRAGONX_TX);
if (txRes) {
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_TX;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting dragonx-tx (%zu MB)...\n", txRes->size / (1024*1024));
if (!extractResource(txRes, dest)) {
success = false;
}
#ifndef _WIN32
else { chmod(dest.c_str(), 0755); }
#endif
}
}
#endif
#ifdef HAS_EMBEDDED_XMRIG
const EmbeddedResource* xmrigRes = getEmbeddedResource(RESOURCE_XMRIG);
if (xmrigRes) {
std::string dest = daemonDir + pathSep + RESOURCE_XMRIG;
if (resourceNeedsUpdate(xmrigRes, dest)) {
if (std::filesystem::exists(dest))
DEBUG_LOGF("[INFO] Updating stale xmrig (size mismatch)...\n");
DEBUG_LOGF("[INFO] Extracting xmrig (%zu MB)...\n", xmrigRes->size / (1024*1024));
if (!extractResource(xmrigRes, dest)) {
success = false;
}
#ifndef _WIN32
else { chmod(dest.c_str(), 0755); }
#endif
}
}
#endif
// Best-effort cleanup of any ".old" binaries left behind by a previous in-use replacement.
// Once the old daemon/xmrig process has exited, the file is no longer locked and removes cleanly;
// if it's still running, the remove fails harmlessly and we retry on the next startup.
{
std::error_code ec;
for (const char* name : { RESOURCE_DRAGONXD, RESOURCE_XMRIG }) {
std::filesystem::remove(daemonDir + pathSep + name + std::string(".old"), ec);
ec.clear();
}
}
return success;
}
std::string getDaemonDirectory()
{
// Daemon binaries live in %APPDATA%/ObsidianDragon/dragonx/ (Windows) or
// ~/.config/ObsidianDragon/dragonx/ (Linux) — separate from the blockchain
// data directory to avoid lock-file conflicts.
std::string obsidianDir = util::Platform::getObsidianDragonDir();
#ifdef _WIN32
return obsidianDir + "\\dragonx";
#else
return obsidianDir + "/dragonx";
#endif
}
bool needsDaemonExtraction()
{
#ifdef HAS_EMBEDDED_DAEMON
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
std::string daemonPath = daemonDir + "\\dragonxd.exe";
#else
std::string daemonPath = daemonDir + "/dragonxd";
#endif
const auto* res = getEmbeddedResource(RESOURCE_DRAGONXD);
return resourceNeedsUpdate(res, daemonPath);
#else
return false;
#endif
}
bool hasDaemonAvailable()
{
#ifdef HAS_EMBEDDED_DAEMON
return true;
#else
// Check if daemon exists alongside the executable
#ifdef _WIN32
return std::filesystem::exists("dragonxd.exe");
#else
return std::filesystem::exists("dragonxd");
#endif
#endif
}
std::string getDaemonPath()
{
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
const char pathSep = '\\';
const char* daemonName = "dragonxd.exe";
#else
const char pathSep = '/';
const char* daemonName = "dragonxd";
#endif
DEBUG_LOGF("[DEBUG] getDaemonPath: daemonDir=%s\n", daemonDir.c_str());
#ifdef HAS_EMBEDDED_DAEMON
// Extract if needed
std::string embeddedPath = daemonDir + pathSep + daemonName;
DEBUG_LOGF("[DEBUG] getDaemonPath: checking embedded path %s\n", embeddedPath.c_str());
if (!std::filesystem::exists(embeddedPath)) {
DEBUG_LOGF("[INFO] getDaemonPath: daemon not found, extracting embedded resources...\n");
extractEmbeddedResources();
}
if (std::filesystem::exists(embeddedPath)) {
DEBUG_LOGF("[INFO] getDaemonPath: found at %s\n", embeddedPath.c_str());
return embeddedPath;
}
#endif
// Check local directory
if (std::filesystem::exists(daemonName)) {
DEBUG_LOGF("[INFO] getDaemonPath: found in local directory\n");
return daemonName;
}
DEBUG_LOGF("[ERROR] getDaemonPath: daemon binary not found anywhere\n");
return "";
}
bool needsXmrigExtraction()
{
#ifdef HAS_EMBEDDED_XMRIG
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
std::string xmrigPath = daemonDir + "\\xmrig.exe";
#else
std::string xmrigPath = daemonDir + "/xmrig";
#endif
const auto* res = getEmbeddedResource(RESOURCE_XMRIG);
return resourceNeedsUpdate(res, xmrigPath);
#else
return false;
#endif
}
bool hasXmrigAvailable()
{
#ifdef HAS_EMBEDDED_XMRIG
return true;
#else
// Check if xmrig exists alongside the executable
#ifdef _WIN32
return std::filesystem::exists("xmrig.exe");
#else
return std::filesystem::exists("xmrig");
#endif
#endif
}
bool forceExtractXmrig()
{
#ifdef HAS_EMBEDDED_XMRIG
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
std::string dest = daemonDir + "\\" + RESOURCE_XMRIG;
#else
std::string dest = daemonDir + "/" + RESOURCE_XMRIG;
#endif
const EmbeddedResource* xmrigRes = getEmbeddedResource(RESOURCE_XMRIG);
if (!xmrigRes) {
DEBUG_LOGF("[ERROR] forceExtractXmrig: no embedded xmrig resource\n");
return false;
}
DEBUG_LOGF("[INFO] forceExtractXmrig: extracting xmrig (%zu MB) to %s\n",
xmrigRes->size / (1024*1024), dest.c_str());
if (!extractResource(xmrigRes, dest)) {
DEBUG_LOGF("[ERROR] forceExtractXmrig: extraction failed\n");
return false;
}
#ifndef _WIN32
// Set executable permission on Linux
chmod(dest.c_str(), 0755);
#endif
return true;
#else
return false;
#endif
}
// Scan a binary blob for the daemon's version stamp: 'v' <maj>.<min>.<rev> optionally followed by
// '-' <commit hash (>=6 hex)>, e.g. "v1.0.2-ddd851dc1". Returns the first match, or "" if none.
static std::string scanBinaryVersion(const uint8_t* data, std::size_t size)
{
if (!data || size < 6) return "";
auto isdig = [](uint8_t c) { return std::isdigit(static_cast<unsigned char>(c)) != 0; };
auto isxd = [](uint8_t c) { return std::isxdigit(static_cast<unsigned char>(c)) != 0; };
for (std::size_t i = 0; i + 5 < size; ++i) {
if (data[i] != 'v') continue;
std::size_t k = i + 1, s;
s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue; // major
if (k >= size || data[k] != '.') continue; ++k;
s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue; // minor
if (k >= size || data[k] != '.') continue; ++k;
s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue; // revision
std::size_t end = k;
if (k < size && data[k] == '-') { // optional -<commit>
std::size_t h = k + 1, hs = h;
while (h < size && isxd(data[h])) ++h;
if (h - hs >= 6) end = h;
}
return std::string(reinterpret_cast<const char*>(data) + i, end - i);
}
return "";
}
DaemonBinaryInfo getInstalledDaemonInfo()
{
DaemonBinaryInfo info;
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
info.path = daemonDir + "\\" + RESOURCE_DRAGONXD;
#else
info.path = daemonDir + "/" + RESOURCE_DRAGONXD;
#endif
std::error_code ec;
if (!std::filesystem::exists(info.path, ec)) return info; // exists stays false
info.exists = true;
info.size = std::filesystem::file_size(info.path, ec);
if (ec) info.size = 0;
auto ftime = std::filesystem::last_write_time(info.path, ec);
if (!ec) {
// Convert filesystem clock → system_clock epoch (pre-C++20 portable approximation).
auto sysTime = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - decltype(ftime)::clock::now() + std::chrono::system_clock::now());
info.modifiedEpoch =
static_cast<std::int64_t>(std::chrono::system_clock::to_time_t(sysTime));
}
// Read the binary and scan for its version stamp (one-off; caller caches the result).
std::ifstream f(info.path, std::ios::binary);
if (f) {
f.seekg(0, std::ios::end);
std::streamoff len = f.tellg();
f.seekg(0, std::ios::beg);
if (len > 0) {
std::vector<uint8_t> buf(static_cast<std::size_t>(len));
f.read(reinterpret_cast<char*>(buf.data()), len);
info.version = scanBinaryVersion(buf.data(), static_cast<std::size_t>(f.gcount()));
}
}
return info;
}
BundledDaemonInfo getBundledDaemonInfo()
{
BundledDaemonInfo info;
#ifdef HAS_EMBEDDED_DAEMON
const EmbeddedResource* res = getEmbeddedResource(RESOURCE_DRAGONXD);
if (res && res->data && res->size > 0) {
info.available = true;
info.size = res->size;
// The embedded bytes are constant for this build — scan once.
static const std::string cachedVersion = scanBinaryVersion(res->data, res->size);
info.version = cachedVersion;
}
#endif
return info;
}
bool reextractBundledDaemon()
{
#ifdef HAS_EMBEDDED_DAEMON
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
const char pathSep = '\\';
#else
const char pathSep = '/';
#endif
bool ok = true;
bool wroteAny = false;
const char* names[] = { RESOURCE_DRAGONXD, RESOURCE_DRAGONX_CLI, RESOURCE_DRAGONX_TX };
for (const char* name : names) {
const EmbeddedResource* res = getEmbeddedResource(name);
if (!res) continue;
std::string dest = daemonDir + pathSep + name;
DEBUG_LOGF("[INFO] reextractBundledDaemon: writing %s (%zu MB)\n", name, res->size / (1024*1024));
if (!extractResource(res, dest)) {
DEBUG_LOGF("[ERROR] reextractBundledDaemon: failed to write %s\n", name);
ok = false;
continue;
}
wroteAny = true;
#ifndef _WIN32
chmod(dest.c_str(), 0755);
#endif
}
return ok && wroteAny;
#else
return false;
#endif
}
std::string getXmrigPath()
{
std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
const char pathSep = '\\';
const char* xmrigName = "xmrig.exe";
#else
const char pathSep = '/';
const char* xmrigName = "xmrig";
#endif
DEBUG_LOGF("[DEBUG] getXmrigPath: daemonDir=%s\n", daemonDir.c_str());
#ifdef HAS_EMBEDDED_XMRIG
// Extract if needed — force re-extract in case Defender deleted it
std::string embeddedPath = daemonDir + pathSep + xmrigName;
DEBUG_LOGF("[DEBUG] getXmrigPath: checking embedded path %s\n", embeddedPath.c_str());
if (!std::filesystem::exists(embeddedPath)) {
DEBUG_LOGF("[DEBUG] getXmrigPath: not found, force re-extracting xmrig\n");
forceExtractXmrig();
}
if (std::filesystem::exists(embeddedPath)) {
DEBUG_LOGF("[INFO] getXmrigPath: found at %s\n", embeddedPath.c_str());
return embeddedPath;
}
#endif
// Check local directory
if (std::filesystem::exists(xmrigName)) {
DEBUG_LOGF("[INFO] getXmrigPath: found in local directory\n");
return xmrigName;
}
// Check alongside the wallet executable (zip / AppImage distributions)
{
std::string exeDir = util::Platform::getExecutableDirectory();
if (!exeDir.empty()) {
std::string exeDirPath = exeDir + "/" + xmrigName;
if (std::filesystem::exists(exeDirPath)) {
DEBUG_LOGF("[INFO] getXmrigPath: found alongside wallet at %s\n", exeDirPath.c_str());
return exeDirPath;
}
}
}
// Check development paths
#ifdef _WIN32
// Not applicable for cross-compiled Windows builds
#else
// Linux development build — resolve relative to executable directory
// (build/bin/), not CWD which may differ at runtime
std::string exeDir = util::Platform::getExecutableDirectory();
std::vector<std::string> devPaths = {
exeDir + "/../../external/xmrig-HAC/xmrig/build/xmrig", // from build/linux/bin/
exeDir + "/../external/xmrig-HAC/xmrig/build/xmrig", // from build/linux/
"../external/xmrig-HAC/xmrig/build/xmrig", // CWD = build/linux/
"../../external/xmrig-HAC/xmrig/build/xmrig", // CWD = build/linux/bin/
"external/xmrig-HAC/xmrig/build/xmrig", // CWD = project root
};
for (const auto& dp : devPaths) {
if (std::filesystem::exists(dp)) {
std::string resolved = std::filesystem::canonical(dp).string();
DEBUG_LOGF("[INFO] getXmrigPath: found dev binary at %s\n", resolved.c_str());
return resolved;
}
}
#endif
DEBUG_LOGF("[ERROR] getXmrigPath: xmrig binary not found anywhere\n");
return "";
}
} // namespace resources
} // namespace dragonx