Files
OrcaSlicer/src/slic3r/Utils/BBLNetworkPlugin.cpp
T
Hanif Koh 138fa26847 Support the 02.08.04 Bambu Network Plug-in Series
The 02.08.02 series appended queue_plate_id to PrintParams and nothing
after it changed the ABI OrcaSlicer calls, so adding the field brings
the current layout up to 02.08.04. Make 02.08.04 the latest series and
drop 02.08.01 from the whitelist: its PrintParams no longer matches, and
its malformed bind table is refused by dyld on macOS 27, so it cannot
load there. A stored 02.08.01 falls back to the latest series through
the existing unsupported-version path.
2026-10-06 17:20:11 +08:00

958 lines
45 KiB
C++

#include "BBLNetworkPlugin.hpp"
#include "NetworkAgent.hpp"
#include <mutex>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <exception>
#include <boost/filesystem/file_status.hpp>
#include <set>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include "bambu_networking.hpp"
#include <string>
#include <vector>
#include <utility>
#include "libslic3r/Utils.hpp"
#include "slic3r/Utils/FileTransferUtils.hpp"
#include <cstddef>
#include <cstring>
#include <cwchar>
#if !defined(_MSC_VER) && !defined(_WIN32)
#include <dlfcn.h>
#endif
namespace Slic3r {
#define BAMBU_SOURCE_LIBRARY "BambuSource"
namespace {
// Named in the load log: the bound generation is what ties a crash report to an ABI choice.
// The label is the whitelist row's series, so it can never drift from the dispatch table.
const char* network_abi_name(NetworkAbi abi)
{
for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i)
if (AVAILABLE_NETWORK_VERSIONS[i].abi == abi)
return AVAILABLE_NETWORK_VERSIONS[i].version;
return "unsupported";
}
} // namespace
// ============================================================================
// Singleton Implementation
// ============================================================================
// Static pointer initialization (null by default, created on first access)
BBLNetworkPlugin* BBLNetworkPlugin::s_instance = nullptr;
BBLNetworkPlugin& BBLNetworkPlugin::instance()
{
static std::once_flag flag;
std::call_once(flag, [] {
s_instance = new BBLNetworkPlugin();
});
return *s_instance;
}
void BBLNetworkPlugin::shutdown()
{
// Note: Do not call instance() after shutdown() - the singleton is destroyed.
if (s_instance) {
delete s_instance;
s_instance = nullptr;
}
}
BBLNetworkPlugin::BBLNetworkPlugin() = default;
BBLNetworkPlugin::~BBLNetworkPlugin()
{
unload(); // unload() destroys the agent first (see the note there)
}
// ============================================================================
// Module Lifecycle
// ============================================================================
int BBLNetworkPlugin::initialize(bool using_backup, const std::string& version)
{
clear_load_error();
std::string library;
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / "plugins";
if (using_backup) {
plugin_folder = plugin_folder / "backup";
}
if (version.empty()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": version is required but not provided";
set_load_error(
"Network library version not specified",
"A version must be specified to load the network library",
""
);
return -1;
}
// Auto-migration: If loading legacy version and versioned library doesn't exist,
// but unversioned legacy library does exist, copy it to versioned format
if (is_legacy_version(version)) {
boost::filesystem::path versioned_path;
boost::filesystem::path legacy_path;
#if defined(_MSC_VER) || defined(_WIN32)
versioned_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll");
legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll");
#elif defined(__WXMAC__)
versioned_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib");
legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib");
#else
versioned_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so");
legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so");
#endif
if (!boost::filesystem::exists(versioned_path) && boost::filesystem::exists(legacy_path)) {
try {
boost::filesystem::copy(legacy_path, versioned_path);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to copy legacy library: " << e.what();
}
}
}
// Load versioned library. In the normal plugins folder a bare series (02.08.01) resolves to
// whatever same-series build is actually on disk (see resolve_library_path); the backup
// folder keeps the exact versioned name.
#if defined(_MSC_VER) || defined(_WIN32)
std::string versioned_name = std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll";
#elif defined(__WXMAC__)
std::string versioned_name = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib";
#else
std::string versioned_name = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so";
#endif
library = using_backup ? (plugin_folder / versioned_name).string()
: resolve_library_path(version);
#if defined(_MSC_VER) || defined(_WIN32)
wchar_t lib_wstr[256];
memset(lib_wstr, 0, sizeof(lib_wstr));
::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0]));
m_networking_module = LoadLibrary(lib_wstr);
if (!m_networking_module) {
std::string library_path = get_libpath_in_current_directory(std::string(BAMBU_NETWORK_LIBRARY));
if (library_path.empty()) {
set_load_error(
"Network library not found",
"Could not locate versioned library: " + library,
library
);
return -1;
}
memset(lib_wstr, 0, sizeof(lib_wstr));
::MultiByteToWideChar(CP_UTF8, NULL, library_path.c_str(), strlen(library_path.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0]));
m_networking_module = LoadLibrary(lib_wstr);
}
#else
m_networking_module = dlopen(library.c_str(), RTLD_LAZY);
if (!m_networking_module) {
char* dll_error = dlerror();
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": dlopen failed: " << (dll_error ? dll_error : "unknown error");
set_load_error(
"Failed to load network library",
dll_error ? std::string(dll_error) : "Unknown dlopen error",
library
);
}
#endif
if (!m_networking_module) {
if (!m_load_error.has_error) {
set_load_error(
"Network library failed to load",
"LoadLibrary/dlopen returned null",
library
);
}
return -1;
}
// Load file transfer interface
InitFTModule(m_networking_module);
// Load all function pointers
load_all_function_pointers();
// Key the generation on the library that actually loaded, not the version asked for:
// resolve_library_path() serves any same-series build. m_get_version is read directly, not
// via get_version(), which would substitute the "00.00.00.00" sentinel and pick no generation.
const std::string loaded_version = m_get_version ? m_get_version() : std::string();
m_network_abi = network_plugin_abi(loaded_version.empty() ? version : loaded_version);
// A library reporting a series this build has no ABI for stays loaded but uncallable -
// check_networking_version() then reports it as incompatible and offers the update flow.
if (m_network_abi == NetworkAbi::Unsupported) {
BOOST_LOG_TRIVIAL(warning) << "BBLNetworkPlugin::initialize: no ABI for version "
<< (loaded_version.empty() ? version : loaded_version) << ", plug-in calls are disabled";
}
BOOST_LOG_TRIVIAL(info) << "BBLNetworkPlugin::initialize: abi=" << network_abi_name(m_network_abi)
<< ", library=" << library
<< ", version=" << (loaded_version.empty() ? "unknown" : loaded_version)
<< ", send_message=" << (m_send_message ? "loaded" : "null")
<< ", start_print=" << (m_start_print ? "loaded" : "null")
<< ", start_local_print=" << (m_start_local_print ? "loaded" : "null")
<< ", get_my_token=" << (m_get_my_token ? "loaded" : "null");
return 0;
}
int BBLNetworkPlugin::unload()
{
// Orca: destroy the plugin agent while its creating DLL is still loaded, so the void* handle
// never dangles into freed memory. A stale m_agent surviving the unload makes create_agent()
// short-circuit on has_agent() after a hot reload, and the next call into the freshly loaded
// DLL dereferences the old-DLL handle -> access violation.
destroy_agent();
UnloadFTModule();
#if defined(_MSC_VER) || defined(_WIN32)
if (m_networking_module) {
FreeLibrary(m_networking_module);
m_networking_module = NULL;
}
if (m_source_module) {
FreeLibrary(m_source_module);
m_source_module = NULL;
}
#else
if (m_networking_module) {
dlclose(m_networking_module);
m_networking_module = NULL;
}
if (m_source_module) {
dlclose(m_source_module);
m_source_module = NULL;
}
#endif
clear_all_function_pointers();
// Safe to reset only because every pointer was nulled just above and every dispatcher is
// guarded on a non-null pointer, so no stale generation is reachable.
m_network_abi = NetworkAbi::Unsupported;
return 0;
}
bool BBLNetworkPlugin::is_loaded() const
{
return m_networking_module != nullptr;
}
std::string BBLNetworkPlugin::get_version() const
{
bool consistent = true;
// Check the debug consistent first
if (m_check_debug_consistent) {
#if defined(NDEBUG)
consistent = m_check_debug_consistent(false);
#else
consistent = m_check_debug_consistent(true);
#endif
}
if (!consistent) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", inconsistent library, return 00.00.00.00!");
return "00.00.00.00";
}
if (m_get_version) {
return m_get_version();
}
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", get_version not supported, return 00.00.00.00!");
return "00.00.00.00";
}
// ============================================================================
// Agent Lifecycle
// ============================================================================
void* BBLNetworkPlugin::create_agent(const std::string& log_dir)
{
if (m_agent) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": reusing existing agent " << m_agent;
return m_agent;
}
if (m_create_agent) {
m_agent = m_create_agent(log_dir);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": created agent " << m_agent;
return m_agent;
}
int BBLNetworkPlugin::destroy_agent()
{
int ret = 0;
if (m_agent && m_destroy_agent) {
ret = m_destroy_agent(m_agent);
}
m_agent = nullptr;
return ret;
}
// ============================================================================
// DLL Module Accessors
// ============================================================================
#if defined(_MSC_VER) || defined(_WIN32)
HMODULE BBLNetworkPlugin::get_source_module()
#else
void* BBLNetworkPlugin::get_source_module()
#endif
{
if ((m_source_module) || (!m_networking_module))
return m_source_module;
std::string library;
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / "plugins";
#if defined(_MSC_VER) || defined(_WIN32)
wchar_t lib_wstr[128];
library = plugin_folder.string() + "/" + std::string(BAMBU_SOURCE_LIBRARY) + ".dll";
memset(lib_wstr, 0, sizeof(lib_wstr));
::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0]));
m_source_module = LoadLibrary(lib_wstr);
if (!m_source_module) {
std::string library_path = get_libpath_in_current_directory(std::string(BAMBU_SOURCE_LIBRARY));
if (library_path.empty()) {
return m_source_module;
}
memset(lib_wstr, 0, sizeof(lib_wstr));
::MultiByteToWideChar(CP_UTF8, NULL, library_path.c_str(), strlen(library_path.c_str()) + 1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0]));
m_source_module = LoadLibrary(lib_wstr);
}
#else
#if defined(__WXMAC__)
library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_SOURCE_LIBRARY) + ".dylib";
#else
library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_SOURCE_LIBRARY) + ".so";
#endif
m_source_module = dlopen(library.c_str(), RTLD_LAZY);
#endif
return m_source_module;
}
void* BBLNetworkPlugin::get_function(const char* name)
{
void* function = nullptr;
if (!m_networking_module)
return function;
#if defined(_MSC_VER) || defined(_WIN32)
function = reinterpret_cast<void*>(GetProcAddress(m_networking_module, name));
#else
function = dlsym(m_networking_module, name);
#endif
return function;
}
// ============================================================================
// Utility Methods
// ============================================================================
std::string BBLNetworkPlugin::get_libpath_in_current_directory(const std::string& library_name)
{
std::string lib_path;
#if defined(_MSC_VER) || defined(_WIN32)
wchar_t file_name[512];
DWORD ret = GetModuleFileNameW(NULL, file_name, 512);
if (!ret) {
return lib_path;
}
int size_needed = ::WideCharToMultiByte(0, 0, file_name, wcslen(file_name), nullptr, 0, nullptr, nullptr);
std::string file_name_string(size_needed, 0);
::WideCharToMultiByte(0, 0, file_name, wcslen(file_name), file_name_string.data(), size_needed, nullptr, nullptr);
std::size_t found = file_name_string.find("orca-slicer.exe");
if (found == (file_name_string.size() - 16)) {
lib_path = library_name + ".dll";
lib_path = file_name_string.replace(found, 16, lib_path);
}
#else
(void)library_name;
#endif
return lib_path;
}
std::string BBLNetworkPlugin::get_versioned_library_path(const std::string& version)
{
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / "plugins";
#if defined(_MSC_VER) || defined(_WIN32)
return (plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll")).string();
#elif defined(__WXMAC__)
return (plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib")).string();
#else
return (plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so")).string();
#endif
}
std::string BBLNetworkPlugin::resolve_library_path(const std::string& version)
{
std::string exact = get_versioned_library_path(version);
if (boost::filesystem::exists(exact))
return exact;
// A bare series (02.08.01) is physically present only as a specific build (02.08.01.53) when
// the startup file-rename was skipped or failed. Resolve to the newest same-series build
// actually on disk. Custom and legacy names are exact and never resolved.
if (is_series_managed_version(version)) {
const std::string series = network_plugin_series(version);
std::string best;
for (const auto& v : scan_plugin_versions())
if (is_series_managed_version(v) && network_plugin_series(v) == series && (best.empty() || v > best))
best = v;
if (!best.empty())
return get_versioned_library_path(best);
}
return exact; // nonexistent -> caller downloads
}
bool BBLNetworkPlugin::versioned_library_exists(const std::string& version)
{
if (version.empty()) return false;
if (boost::filesystem::exists(resolve_library_path(version))) return true;
if (is_legacy_version(version)) {
return legacy_library_exists();
}
return false;
}
bool BBLNetworkPlugin::legacy_library_exists()
{
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / "plugins";
#if defined(_MSC_VER) || defined(_WIN32)
auto legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll");
#elif defined(__WXMAC__)
auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib");
#else
auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so");
#endif
return boost::filesystem::exists(legacy_path);
}
void BBLNetworkPlugin::remove_legacy_library()
{
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / "plugins";
#if defined(_MSC_VER) || defined(_WIN32)
auto legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll");
#elif defined(__WXMAC__)
auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib");
#else
auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so");
#endif
if (boost::filesystem::exists(legacy_path)) {
boost::system::error_code ec;
boost::filesystem::remove(legacy_path, ec);
}
}
std::vector<std::string> BBLNetworkPlugin::scan_plugin_versions()
{
std::vector<std::string> discovered_versions;
std::string data_dir_str = data_dir();
boost::filesystem::path plugin_folder = boost::filesystem::path(data_dir_str) / "plugins";
if (!boost::filesystem::is_directory(plugin_folder)) {
return discovered_versions;
}
#if defined(_MSC_VER) || defined(_WIN32)
std::string prefix = std::string(BAMBU_NETWORK_LIBRARY) + "_";
std::string extension = ".dll";
#elif defined(__WXMAC__)
std::string prefix = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_";
std::string extension = ".dylib";
#else
std::string prefix = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_";
std::string extension = ".so";
#endif
boost::system::error_code ec;
for (auto& entry : boost::filesystem::directory_iterator(plugin_folder, ec)) {
if (ec) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": error iterating directory: " << ec.message();
break;
}
if (!boost::filesystem::is_regular_file(entry.status()))
continue;
std::string filename = entry.path().filename().string();
if (filename.rfind(prefix, 0) != 0)
continue;
if (filename.size() <= extension.size() ||
filename.compare(filename.size() - extension.size(), extension.size(), extension) != 0)
continue;
std::string version = filename.substr(prefix.size(),
filename.size() - prefix.size() - extension.size());
discovered_versions.push_back(version);
}
return discovered_versions;
}
// ============================================================================
// Error Handling
// ============================================================================
void BBLNetworkPlugin::clear_load_error()
{
m_load_error = NetworkLibraryLoadError{};
}
void BBLNetworkPlugin::set_load_error(const std::string& message,
const std::string& technical_details,
const std::string& attempted_path)
{
m_load_error.has_error = true;
m_load_error.message = message;
m_load_error.technical_details = technical_details;
m_load_error.attempted_path = attempted_path;
}
// ============================================================================
// ABI Conversion Helpers
// ============================================================================
PrintParams_Legacy BBLNetworkPlugin::as_legacy(PrintParams& param)
{
PrintParams_Legacy l;
l.dev_id = std::move(param.dev_id);
l.task_name = std::move(param.task_name);
l.project_name = std::move(param.project_name);
l.preset_name = std::move(param.preset_name);
l.filename = std::move(param.filename);
l.config_filename = std::move(param.config_filename);
l.plate_index = param.plate_index;
l.ftp_folder = std::move(param.ftp_folder);
l.ftp_file = std::move(param.ftp_file);
l.ftp_file_md5 = std::move(param.ftp_file_md5);
l.ams_mapping = std::move(param.ams_mapping);
l.ams_mapping_info = std::move(param.ams_mapping_info);
l.connection_type = std::move(param.connection_type);
l.comments = std::move(param.comments);
l.origin_profile_id = param.origin_profile_id;
l.stl_design_id = param.stl_design_id;
l.origin_model_id = std::move(param.origin_model_id);
l.print_type = std::move(param.print_type);
l.dst_file = std::move(param.dst_file);
l.dev_name = std::move(param.dev_name);
l.dev_ip = std::move(param.dev_ip);
l.use_ssl_for_ftp = param.use_ssl_for_ftp;
l.use_ssl_for_mqtt = param.use_ssl_for_mqtt;
l.username = std::move(param.username);
l.password = std::move(param.password);
l.task_bed_leveling = param.task_bed_leveling;
l.task_flow_cali = param.task_flow_cali;
l.task_vibration_cali = param.task_vibration_cali;
l.task_layer_inspect = param.task_layer_inspect;
l.task_record_timelapse = param.task_record_timelapse;
l.task_use_ams = param.task_use_ams;
l.task_bed_type = std::move(param.task_bed_type);
l.extra_options = std::move(param.extra_options);
return l;
}
// Every PrintParams field except the four the 02.08.01 series added
// (task_timelapse_use_internal, extruder_cali_manual_mode, svc_context, slicer_uid) and the
// queue_plate_id 02.08.02 appended.
PrintParams_0203 BBLNetworkPlugin::as_0203(PrintParams& param)
{
PrintParams_0203 p;
p.dev_id = std::move(param.dev_id);
p.task_name = std::move(param.task_name);
p.project_name = std::move(param.project_name);
p.preset_name = std::move(param.preset_name);
p.filename = std::move(param.filename);
p.config_filename = std::move(param.config_filename);
p.plate_index = param.plate_index;
p.ftp_folder = std::move(param.ftp_folder);
p.ftp_file = std::move(param.ftp_file);
p.ftp_file_md5 = std::move(param.ftp_file_md5);
p.nozzle_mapping = std::move(param.nozzle_mapping);
p.ams_mapping = std::move(param.ams_mapping);
p.ams_mapping2 = std::move(param.ams_mapping2);
p.ams_mapping_info = std::move(param.ams_mapping_info);
p.nozzles_info = std::move(param.nozzles_info);
p.connection_type = std::move(param.connection_type);
p.comments = std::move(param.comments);
p.origin_profile_id = param.origin_profile_id;
p.stl_design_id = param.stl_design_id;
p.origin_model_id = std::move(param.origin_model_id);
p.print_type = std::move(param.print_type);
p.dst_file = std::move(param.dst_file);
p.dev_name = std::move(param.dev_name);
p.dev_ip = std::move(param.dev_ip);
p.use_ssl_for_ftp = param.use_ssl_for_ftp;
p.use_ssl_for_mqtt = param.use_ssl_for_mqtt;
p.username = std::move(param.username);
p.password = std::move(param.password);
p.task_bed_leveling = param.task_bed_leveling;
p.task_flow_cali = param.task_flow_cali;
p.task_vibration_cali = param.task_vibration_cali;
p.task_layer_inspect = param.task_layer_inspect;
p.task_record_timelapse = param.task_record_timelapse;
p.task_use_ams = param.task_use_ams;
p.task_bed_type = std::move(param.task_bed_type);
p.extra_options = std::move(param.extra_options);
p.auto_bed_leveling = param.auto_bed_leveling;
p.auto_flow_cali = param.auto_flow_cali;
p.auto_offset_cali = param.auto_offset_cali;
p.task_ext_change_assist = param.task_ext_change_assist;
p.try_emmc_print = param.try_emmc_print;
return p;
}
// ============================================================================
// Function Pointer Loading
// ============================================================================
void BBLNetworkPlugin::load_all_function_pointers()
{
m_check_debug_consistent = reinterpret_cast<func_check_debug_consistent>(get_function("bambu_network_check_debug_consistent"));
m_get_version = reinterpret_cast<func_get_version>(get_function("bambu_network_get_version"));
m_create_agent = reinterpret_cast<func_create_agent>(get_function("bambu_network_create_agent"));
m_destroy_agent = reinterpret_cast<func_destroy_agent>(get_function("bambu_network_destroy_agent"));
m_init_log = reinterpret_cast<func_init_log>(get_function("bambu_network_init_log"));
m_set_config_dir = reinterpret_cast<func_set_config_dir>(get_function("bambu_network_set_config_dir"));
m_set_cert_file = reinterpret_cast<func_set_cert_file>(get_function("bambu_network_set_cert_file"));
m_set_country_code = reinterpret_cast<func_set_country_code>(get_function("bambu_network_set_country_code"));
m_start = reinterpret_cast<func_start>(get_function("bambu_network_start"));
m_set_on_ssdp_msg_fn = reinterpret_cast<func_set_on_ssdp_msg_fn>(get_function("bambu_network_set_on_ssdp_msg_fn"));
m_set_on_printer_connected_fn = reinterpret_cast<func_set_on_printer_connected_fn>(get_function("bambu_network_set_on_printer_connected_fn"));
m_set_on_server_connected_fn = reinterpret_cast<func_set_on_server_connected_fn>(get_function("bambu_network_set_on_server_connected_fn"));
m_set_on_http_error_fn = reinterpret_cast<func_set_on_http_error_fn>(get_function("bambu_network_set_on_http_error_fn"));
m_set_get_country_code_fn = reinterpret_cast<func_set_get_country_code_fn>(get_function("bambu_network_set_get_country_code_fn"));
m_set_on_subscribe_failure_fn = reinterpret_cast<func_set_on_subscribe_failure_fn>(get_function("bambu_network_set_on_subscribe_failure_fn"));
m_set_on_message_fn = reinterpret_cast<func_set_on_message_fn>(get_function("bambu_network_set_on_message_fn"));
m_set_on_user_message_fn = reinterpret_cast<func_set_on_user_message_fn>(get_function("bambu_network_set_on_user_message_fn"));
m_set_on_local_connect_fn = reinterpret_cast<func_set_on_local_connect_fn>(get_function("bambu_network_set_on_local_connect_fn"));
m_set_on_local_message_fn = reinterpret_cast<func_set_on_local_message_fn>(get_function("bambu_network_set_on_local_message_fn"));
m_set_queue_on_main_fn = reinterpret_cast<func_set_queue_on_main_fn>(get_function("bambu_network_set_queue_on_main_fn"));
m_connect_server = reinterpret_cast<func_connect_server>(get_function("bambu_network_connect_server"));
m_is_server_connected = reinterpret_cast<func_is_server_connected>(get_function("bambu_network_is_server_connected"));
m_refresh_connection = reinterpret_cast<func_refresh_connection>(get_function("bambu_network_refresh_connection"));
m_start_subscribe = reinterpret_cast<func_start_subscribe>(get_function("bambu_network_start_subscribe"));
m_stop_subscribe = reinterpret_cast<func_stop_subscribe>(get_function("bambu_network_stop_subscribe"));
m_add_subscribe = reinterpret_cast<func_add_subscribe>(get_function("bambu_network_add_subscribe"));
m_del_subscribe = reinterpret_cast<func_del_subscribe>(get_function("bambu_network_del_subscribe"));
m_enable_multi_machine = reinterpret_cast<func_enable_multi_machine>(get_function("bambu_network_enable_multi_machine"));
m_send_message = reinterpret_cast<func_send_message>(get_function("bambu_network_send_message"));
m_connect_printer = reinterpret_cast<func_connect_printer>(get_function("bambu_network_connect_printer"));
m_disconnect_printer = reinterpret_cast<func_disconnect_printer>(get_function("bambu_network_disconnect_printer"));
m_send_message_to_printer = reinterpret_cast<func_send_message_to_printer>(get_function("bambu_network_send_message_to_printer"));
m_check_cert = reinterpret_cast<func_check_cert>(get_function("bambu_network_update_cert"));
m_install_device_cert = reinterpret_cast<func_install_device_cert>(get_function("bambu_network_install_device_cert"));
m_start_discovery = reinterpret_cast<func_start_discovery>(get_function("bambu_network_start_discovery"));
m_change_user = reinterpret_cast<func_change_user>(get_function("bambu_network_change_user"));
m_is_user_login = reinterpret_cast<func_is_user_login>(get_function("bambu_network_is_user_login"));
m_user_logout = reinterpret_cast<func_user_logout>(get_function("bambu_network_user_logout"));
m_get_user_id = reinterpret_cast<func_get_user_id>(get_function("bambu_network_get_user_id"));
m_get_user_name = reinterpret_cast<func_get_user_name>(get_function("bambu_network_get_user_name"));
m_get_user_avatar = reinterpret_cast<func_get_user_avatar>(get_function("bambu_network_get_user_avatar"));
m_get_user_nickanme = reinterpret_cast<func_get_user_nickanme>(get_function("bambu_network_get_user_nickanme"));
m_build_login_cmd = reinterpret_cast<func_build_login_cmd>(get_function("bambu_network_build_login_cmd"));
m_build_logout_cmd = reinterpret_cast<func_build_logout_cmd>(get_function("bambu_network_build_logout_cmd"));
m_build_login_info = reinterpret_cast<func_build_login_info>(get_function("bambu_network_build_login_info"));
m_ping_bind = reinterpret_cast<func_ping_bind>(get_function("bambu_network_ping_bind"));
m_bind_detect = reinterpret_cast<func_bind_detect>(get_function("bambu_network_bind_detect"));
m_set_server_callback = reinterpret_cast<func_set_server_callback>(get_function("bambu_network_set_server_callback"));
m_bind = reinterpret_cast<func_bind>(get_function("bambu_network_bind"));
m_unbind = reinterpret_cast<func_unbind>(get_function("bambu_network_unbind"));
m_get_bambulab_host = reinterpret_cast<func_get_bambulab_host>(get_function("bambu_network_get_bambulab_host"));
m_get_user_selected_machine = reinterpret_cast<func_get_user_selected_machine>(get_function("bambu_network_get_user_selected_machine"));
m_set_user_selected_machine = reinterpret_cast<func_set_user_selected_machine>(get_function("bambu_network_set_user_selected_machine"));
m_start_print = reinterpret_cast<func_start_print>(get_function("bambu_network_start_print"));
m_start_local_print_with_record = reinterpret_cast<func_start_local_print_with_record>(get_function("bambu_network_start_local_print_with_record"));
m_start_send_gcode_to_sdcard = reinterpret_cast<func_start_send_gcode_to_sdcard>(get_function("bambu_network_start_send_gcode_to_sdcard"));
m_start_local_print = reinterpret_cast<func_start_local_print>(get_function("bambu_network_start_local_print"));
m_start_sdcard_print = reinterpret_cast<func_start_sdcard_print>(get_function("bambu_network_start_sdcard_print"));
m_get_user_presets = reinterpret_cast<func_get_user_presets>(get_function("bambu_network_get_user_presets"));
m_request_setting_id = reinterpret_cast<func_request_setting_id>(get_function("bambu_network_request_setting_id"));
m_put_setting = reinterpret_cast<func_put_setting>(get_function("bambu_network_put_setting"));
m_get_setting_list = reinterpret_cast<func_get_setting_list>(get_function("bambu_network_get_setting_list"));
m_get_setting_list2 = reinterpret_cast<func_get_setting_list2>(get_function("bambu_network_get_setting_list2"));
m_delete_setting = reinterpret_cast<func_delete_setting>(get_function("bambu_network_delete_setting"));
m_set_extra_http_header = reinterpret_cast<func_set_extra_http_header>(get_function("bambu_network_set_extra_http_header"));
m_get_my_message = reinterpret_cast<func_get_my_message>(get_function("bambu_network_get_my_message"));
m_check_user_task_report = reinterpret_cast<func_check_user_task_report>(get_function("bambu_network_check_user_task_report"));
m_get_user_print_info = reinterpret_cast<func_get_user_print_info>(get_function("bambu_network_get_user_print_info"));
m_get_user_tasks = reinterpret_cast<func_get_user_tasks>(get_function("bambu_network_get_user_tasks"));
m_get_printer_firmware = reinterpret_cast<func_get_printer_firmware>(get_function("bambu_network_get_printer_firmware"));
m_get_task_plate_index = reinterpret_cast<func_get_task_plate_index>(get_function("bambu_network_get_task_plate_index"));
m_get_user_info = reinterpret_cast<func_get_user_info>(get_function("bambu_network_get_user_info"));
m_request_bind_ticket = reinterpret_cast<func_request_bind_ticket>(get_function("bambu_network_request_bind_ticket"));
m_get_subtask_info = reinterpret_cast<func_get_subtask_info>(get_function("bambu_network_get_subtask_info"));
m_get_slice_info = reinterpret_cast<func_get_slice_info>(get_function("bambu_network_get_slice_info"));
m_query_bind_status = reinterpret_cast<func_query_bind_status>(get_function("bambu_network_query_bind_status"));
m_modify_printer_name = reinterpret_cast<func_modify_printer_name>(get_function("bambu_network_modify_printer_name"));
m_get_camera_url = reinterpret_cast<func_get_camera_url>(get_function("bambu_network_get_camera_url"));
m_get_design_staffpick = reinterpret_cast<func_get_design_staffpick>(get_function("bambu_network_get_design_staffpick"));
m_start_publish = reinterpret_cast<func_start_pubilsh>(get_function("bambu_network_start_publish"));
m_get_model_publish_url = reinterpret_cast<func_get_model_publish_url>(get_function("bambu_network_get_model_publish_url"));
m_get_subtask = reinterpret_cast<func_get_subtask>(get_function("bambu_network_get_subtask"));
m_get_model_mall_home_url = reinterpret_cast<func_get_model_mall_home_url>(get_function("bambu_network_get_model_mall_home_url"));
m_get_model_mall_detail_url = reinterpret_cast<func_get_model_mall_detail_url>(get_function("bambu_network_get_model_mall_detail_url"));
m_get_my_profile = reinterpret_cast<func_get_my_profile>(get_function("bambu_network_get_my_profile"));
m_get_my_token = reinterpret_cast<func_get_my_token>(get_function("bambu_network_get_my_token"));
m_track_enable = reinterpret_cast<func_track_enable>(get_function("bambu_network_track_enable"));
m_track_remove_files = reinterpret_cast<func_track_remove_files>(get_function("bambu_network_track_remove_files"));
m_track_event = reinterpret_cast<func_track_event>(get_function("bambu_network_track_event"));
m_track_header = reinterpret_cast<func_track_header>(get_function("bambu_network_track_header"));
m_track_update_property = reinterpret_cast<func_track_update_property>(get_function("bambu_network_track_update_property"));
m_track_get_property = reinterpret_cast<func_track_get_property>(get_function("bambu_network_track_get_property"));
m_put_model_mall_rating = reinterpret_cast<func_put_model_mall_rating_url>(get_function("bambu_network_put_model_mall_rating"));
m_get_oss_config = reinterpret_cast<func_get_oss_config>(get_function("bambu_network_get_oss_config"));
m_put_rating_picture_oss = reinterpret_cast<func_put_rating_picture_oss>(get_function("bambu_network_put_rating_picture_oss"));
m_get_model_mall_rating_result = reinterpret_cast<func_get_model_mall_rating_result>(get_function("bambu_network_get_model_mall_rating"));
m_get_mw_user_preference = reinterpret_cast<func_get_mw_user_preference>(get_function("bambu_network_get_mw_user_preference"));
m_get_mw_user_4ulist = reinterpret_cast<func_get_mw_user_4ulist>(get_function("bambu_network_get_mw_user_4ulist"));
// Bound late; anything a generation does not export resolves to null so callers no-op.
// See the typedefs for which generation introduced each of these.
m_set_on_user_login_fn = reinterpret_cast<func_set_on_user_login_fn>(get_function("bambu_network_set_on_user_login_fn"));
m_get_studio_info_url = reinterpret_cast<func_get_studio_info_url>(get_function("bambu_network_get_studio_info_url"));
m_report_consent = reinterpret_cast<func_report_consent>(get_function("bambu_network_report_consent"));
m_get_camera_url_for_golive = reinterpret_cast<func_get_camera_url_for_golive>(get_function("bambu_network_get_camera_url_for_golive"));
m_get_hms_snapshot = reinterpret_cast<func_get_hms_snapshot>(get_function("bambu_network_get_hms_snapshot"));
m_get_filament_spools = reinterpret_cast<func_get_filament_spools>(get_function("bambu_network_get_filament_spools"));
m_create_filament_spool = reinterpret_cast<func_create_filament_spool>(get_function("bambu_network_create_filament_spool"));
m_update_filament_spool = reinterpret_cast<func_update_filament_spool>(get_function("bambu_network_update_filament_spool"));
m_delete_filament_spools = reinterpret_cast<func_delete_filament_spools>(get_function("bambu_network_delete_filament_spools"));
m_get_filament_config = reinterpret_cast<func_get_filament_config>(get_function("bambu_network_get_filament_config"));
m_sync_ams_filaments = reinterpret_cast<func_sync_ams_filaments>(get_function("bambu_network_sync_ams_filaments"));
}
void BBLNetworkPlugin::clear_all_function_pointers()
{
m_check_debug_consistent = nullptr;
m_get_version = nullptr;
m_create_agent = nullptr;
m_destroy_agent = nullptr;
m_init_log = nullptr;
m_set_config_dir = nullptr;
m_set_cert_file = nullptr;
m_set_country_code = nullptr;
m_start = nullptr;
m_set_on_ssdp_msg_fn = nullptr;
m_set_on_printer_connected_fn = nullptr;
m_set_on_server_connected_fn = nullptr;
m_set_on_http_error_fn = nullptr;
m_set_get_country_code_fn = nullptr;
m_set_on_subscribe_failure_fn = nullptr;
m_set_on_message_fn = nullptr;
m_set_on_user_message_fn = nullptr;
m_set_on_local_connect_fn = nullptr;
m_set_on_local_message_fn = nullptr;
m_set_queue_on_main_fn = nullptr;
m_connect_server = nullptr;
m_is_server_connected = nullptr;
m_refresh_connection = nullptr;
m_start_subscribe = nullptr;
m_stop_subscribe = nullptr;
m_add_subscribe = nullptr;
m_del_subscribe = nullptr;
m_enable_multi_machine = nullptr;
m_send_message = nullptr;
m_connect_printer = nullptr;
m_disconnect_printer = nullptr;
m_send_message_to_printer = nullptr;
m_check_cert = nullptr;
m_install_device_cert = nullptr;
m_start_discovery = nullptr;
m_change_user = nullptr;
m_is_user_login = nullptr;
m_user_logout = nullptr;
m_get_user_id = nullptr;
m_get_user_name = nullptr;
m_get_user_avatar = nullptr;
m_get_user_nickanme = nullptr;
m_build_login_cmd = nullptr;
m_build_logout_cmd = nullptr;
m_build_login_info = nullptr;
m_ping_bind = nullptr;
m_bind_detect = nullptr;
m_set_server_callback = nullptr;
m_bind = nullptr;
m_unbind = nullptr;
m_get_bambulab_host = nullptr;
m_get_user_selected_machine = nullptr;
m_set_user_selected_machine = nullptr;
m_start_print = nullptr;
m_start_local_print_with_record = nullptr;
m_start_send_gcode_to_sdcard = nullptr;
m_start_local_print = nullptr;
m_start_sdcard_print = nullptr;
m_get_user_presets = nullptr;
m_request_setting_id = nullptr;
m_put_setting = nullptr;
m_get_setting_list = nullptr;
m_get_setting_list2 = nullptr;
m_delete_setting = nullptr;
m_set_extra_http_header = nullptr;
m_get_my_message = nullptr;
m_check_user_task_report = nullptr;
m_get_user_print_info = nullptr;
m_get_user_tasks = nullptr;
m_get_printer_firmware = nullptr;
m_get_task_plate_index = nullptr;
m_get_user_info = nullptr;
m_request_bind_ticket = nullptr;
m_get_subtask_info = nullptr;
m_get_slice_info = nullptr;
m_query_bind_status = nullptr;
m_modify_printer_name = nullptr;
m_get_camera_url = nullptr;
m_get_design_staffpick = nullptr;
m_start_publish = nullptr;
m_get_model_publish_url = nullptr;
m_get_subtask = nullptr;
m_get_model_mall_home_url = nullptr;
m_get_model_mall_detail_url = nullptr;
m_get_my_profile = nullptr;
m_get_my_token = nullptr;
m_track_enable = nullptr;
m_track_remove_files = nullptr;
m_track_event = nullptr;
m_track_header = nullptr;
m_track_update_property = nullptr;
m_track_get_property = nullptr;
m_put_model_mall_rating = nullptr;
m_get_oss_config = nullptr;
m_put_rating_picture_oss = nullptr;
m_get_model_mall_rating_result = nullptr;
m_get_mw_user_preference = nullptr;
m_get_mw_user_4ulist = nullptr;
m_set_on_user_login_fn = nullptr;
m_get_studio_info_url = nullptr;
m_report_consent = nullptr;
m_get_camera_url_for_golive = nullptr;
m_get_hms_snapshot = nullptr;
m_get_filament_spools = nullptr;
m_create_filament_spool = nullptr;
m_update_filament_spool = nullptr;
m_delete_filament_spools = nullptr;
m_get_filament_config = nullptr;
m_sync_ams_filaments = nullptr;
}
std::vector<NetworkLibraryVersionInfo> get_all_available_versions()
{
// get_version() reports the "00.00.00.00" sentinel when nothing is loaded; resolve
// that here so the list builder only ever sees a real version or an empty string.
const BBLNetworkPlugin& plugin = BBLNetworkPlugin::instance();
return get_all_available_versions(plugin.is_loaded() ? plugin.get_version() : std::string());
}
std::vector<NetworkLibraryVersionInfo> get_all_available_versions(const std::string& loaded_version)
{
std::vector<NetworkLibraryVersionInfo> result;
std::set<std::string> all_known_versions;
for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) {
result.push_back(NetworkLibraryVersionInfo::from_static(AVAILABLE_NETWORK_VERSIONS[i]));
all_known_versions.insert(AVAILABLE_NETWORK_VERSIONS[i].version);
}
std::vector<std::string> discovered = BBLNetworkPlugin::scan_plugin_versions();
// A managed build (pure dotted-numeric AA.BB.CC[.DD]) is represented by its series entry
// above - the OTA-installed 02.08.01.53 and a bare 02.08.01 both collapse into the single
// 02.08.01 row. Only a custom-named build a user dropped in (02.08.01_custom, ..-dev) earns
// its own row, and only when its series is one this build can actually load. The part past
// the series is stored as the "suffix" so the entry sorts and renders nested under it.
for (const auto& version : discovered) {
if (all_known_versions.count(version) > 0)
continue;
if (is_series_managed_version(version))
continue;
if (!is_supported_network_version(version))
continue;
const std::string series = network_plugin_series(version);
const std::string sfx = version.size() > series.size() ? version.substr(series.size()) : version;
result.push_back(NetworkLibraryVersionInfo::from_discovered(version, series, sfx));
all_known_versions.insert(version);
}
// Newest first. Version components are fixed-width and zero-padded, so a plain
// string compare orders them numerically, and the legacy series sorts last on its
// own. Suffixed dev builds sort directly under the base version they build on.
std::sort(result.begin(), result.end(),
[](const NetworkLibraryVersionInfo& a, const NetworkLibraryVersionInfo& b) {
if (a.base_version != b.base_version) return a.base_version > b.base_version;
return a.suffix < b.suffix;
});
const std::string loaded_series = network_plugin_series(loaded_version);
for (auto& info : result) {
// A managed (series) entry matches when a managed build of the same series is loaded -
// the loaded plug-in reports its full build (02.08.01.53) but the row is the series.
// Custom and legacy entries match their exact reported version.
info.is_loaded = !loaded_version.empty() &&
(info.version == loaded_version ||
(is_series_managed_version(info.version) && is_series_managed_version(loaded_version) &&
network_plugin_series(info.version) == loaded_series));
info.is_latest = false;
}
// "(Latest)" goes on the highest full version in the list, which after the sort is
// simply the first entry without a dev suffix - an OTA-installed build can be newer
// than the newest whitelisted entry. get_latest_network_version() intentionally
// keeps returning the static whitelist default, which drives the download and
// update-check decisions.
auto latest = std::find_if(result.begin(), result.end(),
[](const NetworkLibraryVersionInfo& info) { return info.suffix.empty(); });
if (latest != result.end())
latest->is_latest = true;
return result;
}
} // namespace Slic3r