#include "embedded_resources.h" #include "../util/platform.h" #include "../util/logger.h" #include #include #include #include #include #include #ifdef _WIN32 #include #include #else #include #include #include #include #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 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(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(t->size)) continue; // up to date } std::ofstream f(dest, std::ios::binary); if (f.is_open()) { f.write(reinterpret_cast(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(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(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 /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' .. optionally followed by // '-' =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(c)) != 0; }; auto isxd = [](uint8_t c) { return std::isxdigit(static_cast(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 - 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(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( ftime - decltype(ftime)::clock::now() + std::chrono::system_clock::now()); info.modifiedEpoch = static_cast(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 buf(static_cast(len)); f.read(reinterpret_cast(buf.data()), len); info.version = scanBinaryVersion(buf.data(), static_cast(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 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