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>
781 lines
27 KiB
C++
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
|