/* * Registry Editor Plugin — Windows registry browser/editor. * * Exports the standard Overlord plugin ABI: * PluginOnLoad, PluginOnEvent, PluginOnUnload, PluginSetCallback, * PluginGetRuntime (returns "cpp") * * Build (Windows / MSVC): * cl /LD /EHsc /O2 plugin.cpp /Fe:regedit-windows-amd64.dll advapi32.lib * * Build (Windows / MinGW): * x86_64-w64-mingw32-g++ -shared -O2 -o regedit-windows-amd64.dll plugin.cpp -ladvapi32 */ #include #include #include #include #include #include #include #include #include #ifdef _WIN32 # define EXPORT extern "C" __declspec(dllexport) # define WIN32_LEAN_AND_MEAN # include # include # pragma comment(lib, "advapi32.lib") BOOL WINAPI DllMain(HINSTANCE hDll, DWORD reason, LPVOID reserved) { (void)hDll; (void)reason; (void)reserved; return TRUE; } #else # define EXPORT extern "C" __attribute__((visibility("default"))) #endif #ifdef _WIN32 typedef void (__stdcall *host_callback_t)( const char *event, uintptr_t eventLen, const char *payload, uintptr_t payloadLen); #else typedef void (*host_callback_t)( uintptr_t ctx, const char *event, int eventLen, const char *payload, int payloadLen); #endif static std::mutex g_mu; static host_callback_t g_callback = nullptr; #ifndef _WIN32 static uintptr_t g_callback_ctx = 0; #endif static void send_event(const char *event, const std::string &payload) { std::lock_guard lk(g_mu); if (!g_callback) return; #ifdef _WIN32 g_callback(event, static_cast(strlen(event)), payload.c_str(), static_cast(payload.size())); #else g_callback(g_callback_ctx, event, static_cast(strlen(event)), payload.c_str(), static_cast(payload.size())); #endif } static std::string trim(const std::string &s) { size_t a = 0; while (a < s.size() && std::isspace(static_cast(s[a]))) a++; size_t b = s.size(); while (b > a && std::isspace(static_cast(s[b - 1]))) b--; return s.substr(a, b - a); } static std::string json_escape(const std::string &s) { std::string out; out.reserve(s.size() + 16); for (unsigned char c : s) { switch (c) { case '\\': out += "\\\\"; break; case '"': out += "\\\""; break; case '\b': out += "\\b"; break; case '\f': out += "\\f"; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: if (c < 0x20) { char buf[8]; std::snprintf(buf, sizeof(buf), "\\u%04x", c); out += buf; } else { out += static_cast(c); } } } return out; } static std::string ok_payload(const std::string &action, const std::string &extra = "{}") { std::string body = extra; if (body.empty()) body = "{}"; if (body.front() == '{') body.erase(body.begin()); if (!body.empty() && body.back() == '}') body.pop_back(); std::string out = "{\"ok\":true,\"action\":\"" + json_escape(action) + "\""; if (!body.empty()) out += "," + body; out += "}"; return out; } static std::string err_payload(const std::string &action, const std::string &error) { return "{\"ok\":false,\"action\":\"" + json_escape(action) + "\",\"error\":\"" + json_escape(error) + "\"}"; } static std::string json_string(const char *json, int len, const char *key) { if (!json || len <= 0) return ""; std::string s(json, static_cast(len)); std::string needle = std::string("\"") + key + "\""; size_t p = s.find(needle); if (p == std::string::npos) return ""; p = s.find(':', p + needle.size()); if (p == std::string::npos) return ""; p++; while (p < s.size() && std::isspace(static_cast(s[p]))) p++; if (p >= s.size() || s[p] != '"') return ""; p++; std::string out; while (p < s.size()) { char c = s[p++]; if (c == '"') break; if (c == '\\' && p < s.size()) { char e = s[p++]; switch (e) { case 'n': out += '\n'; break; case 'r': out += '\r'; break; case 't': out += '\t'; break; case 'b': out += '\b'; break; case 'f': out += '\f'; break; case '\\': out += '\\'; break; case '"': out += '"'; break; default: out += e; break; } } else { out += c; } } return out; } #ifdef _WIN32 static std::wstring widen(const std::string &s) { if (s.empty()) return L""; int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), nullptr, 0); std::wstring w(static_cast(n), L'\0'); MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), &w[0], n); return w; } static std::string narrow(const std::wstring &w) { if (w.empty()) return ""; int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), nullptr, 0, nullptr, nullptr); std::string s(static_cast(n), '\0'); WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), &s[0], n, nullptr, nullptr); return s; } struct RegPath { HKEY root; std::wstring subkey; std::string root_name; }; static bool parse_path(const std::string &path_in, RegPath &out, std::string &error) { std::string path = trim(path_in); std::replace(path.begin(), path.end(), '/', '\\'); while (!path.empty() && path.back() == '\\') path.pop_back(); size_t slash = path.find('\\'); std::string root = slash == std::string::npos ? path : path.substr(0, slash); std::string sub = slash == std::string::npos ? "" : path.substr(slash + 1); std::string up = root; std::transform(up.begin(), up.end(), up.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); if (up == "HKCR" || up == "HKEY_CLASSES_ROOT") { out.root = HKEY_CLASSES_ROOT; out.root_name = "HKCR"; } else if (up == "HKCU" || up == "HKEY_CURRENT_USER") { out.root = HKEY_CURRENT_USER; out.root_name = "HKCU"; } else if (up == "HKLM" || up == "HKEY_LOCAL_MACHINE") { out.root = HKEY_LOCAL_MACHINE; out.root_name = "HKLM"; } else if (up == "HKU" || up == "HKEY_USERS") { out.root = HKEY_USERS; out.root_name = "HKU"; } else if (up == "HKCC" || up == "HKEY_CURRENT_CONFIG") { out.root = HKEY_CURRENT_CONFIG; out.root_name = "HKCC"; } else { error = "Unknown root hive: " + root; return false; } out.subkey = widen(sub); return true; } static std::string full_path(const RegPath &p) { std::string s = p.root_name; if (!p.subkey.empty()) s += "\\" + narrow(p.subkey); return s; } static std::string win_error(LSTATUS code) { wchar_t *msg = nullptr; DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; DWORD len = FormatMessageW(flags, nullptr, static_cast(code), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&msg), 0, nullptr); std::string text; if (len && msg) { text = narrow(std::wstring(msg, msg + len)); LocalFree(msg); while (!text.empty() && (text.back() == '\r' || text.back() == '\n' || text.back() == ' ')) { text.pop_back(); } } char buf[64]; std::snprintf(buf, sizeof(buf), "Windows error %ld", static_cast(code)); return text.empty() ? std::string(buf) : text + " (" + buf + ")"; } static const char *type_name(DWORD type) { switch (type) { case REG_SZ: return "REG_SZ"; case REG_EXPAND_SZ: return "REG_EXPAND_SZ"; case REG_DWORD: return "REG_DWORD"; case REG_QWORD: return "REG_QWORD"; case REG_BINARY: return "REG_BINARY"; case REG_MULTI_SZ: return "REG_MULTI_SZ"; default: return "REG_NONE"; } } static DWORD type_from_name(const std::string &name) { if (name == "REG_SZ") return REG_SZ; if (name == "REG_EXPAND_SZ") return REG_EXPAND_SZ; if (name == "REG_DWORD") return REG_DWORD; if (name == "REG_QWORD") return REG_QWORD; if (name == "REG_BINARY") return REG_BINARY; if (name == "REG_MULTI_SZ") return REG_MULTI_SZ; return REG_SZ; } static std::string hex_bytes(const BYTE *data, DWORD bytes) { static const char *digits = "0123456789abcdef"; std::string out; for (DWORD i = 0; i < bytes; i++) { if (i) out += ' '; out += digits[(data[i] >> 4) & 0xf]; out += digits[data[i] & 0xf]; } return out; } static bool parse_hex_bytes(const std::string &text, std::vector &out) { std::string h; for (unsigned char c : text) { if (std::isxdigit(c)) h += static_cast(c); } if (h.size() % 2 != 0) return false; out.clear(); for (size_t i = 0; i < h.size(); i += 2) { unsigned int v = 0; std::stringstream ss; ss << std::hex << h.substr(i, 2); ss >> v; out.push_back(static_cast(v & 0xff)); } return true; } static std::string value_to_text(DWORD type, const std::vector &data) { if (type == REG_DWORD && data.size() >= 4) { DWORD v = *reinterpret_cast(data.data()); return std::to_string(v); } if (type == REG_QWORD && data.size() >= 8) { unsigned long long v = *reinterpret_cast(data.data()); return std::to_string(v); } if (type == REG_SZ || type == REG_EXPAND_SZ) { const wchar_t *w = reinterpret_cast(data.data()); size_t chars = data.size() / sizeof(wchar_t); if (chars && w[chars - 1] == L'\0') chars--; return narrow(std::wstring(w, w + chars)); } if (type == REG_MULTI_SZ) { const wchar_t *w = reinterpret_cast(data.data()); size_t chars = data.size() / sizeof(wchar_t); std::string out; size_t start = 0; for (size_t i = 0; i < chars; i++) { if (w[i] == L'\0') { if (i == start) break; if (!out.empty()) out += "\n"; out += narrow(std::wstring(w + start, w + i)); start = i + 1; } } return out; } return hex_bytes(data.data(), static_cast(data.size())); } static bool text_to_value(DWORD type, const std::string &text, std::vector &out, std::string &error) { out.clear(); if (type == REG_DWORD) { char *end = nullptr; unsigned long v = std::strtoul(text.c_str(), &end, 0); if (end == text.c_str()) { error = "Invalid DWORD"; return false; } out.resize(4); std::memcpy(out.data(), &v, 4); return true; } if (type == REG_QWORD) { char *end = nullptr; unsigned long long v = std::strtoull(text.c_str(), &end, 0); if (end == text.c_str()) { error = "Invalid QWORD"; return false; } out.resize(8); std::memcpy(out.data(), &v, 8); return true; } if (type == REG_BINARY) { if (!parse_hex_bytes(text, out)) { error = "Invalid binary hex"; return false; } return true; } if (type == REG_MULTI_SZ) { std::wstring all; std::stringstream ss(text); std::string line; while (std::getline(ss, line)) { std::wstring w = widen(line); all.append(w); all.push_back(L'\0'); } all.push_back(L'\0'); out.resize(all.size() * sizeof(wchar_t)); std::memcpy(out.data(), all.data(), out.size()); return true; } std::wstring w = widen(text); w.push_back(L'\0'); out.resize(w.size() * sizeof(wchar_t)); std::memcpy(out.data(), w.data(), out.size()); return true; } static std::string reg_quote(const std::string &s) { std::string out = "\""; for (char c : s) { if (c == '\\' || c == '"') out += '\\'; out += c; } out += "\""; return out; } static std::string reg_hex_list(const BYTE *data, DWORD size) { static const char *digits = "0123456789abcdef"; std::string out; for (DWORD i = 0; i < size; i++) { if (i) out += ","; out += digits[(data[i] >> 4) & 0xf]; out += digits[data[i] & 0xf]; } return out; } static std::string value_to_reg_line(const std::string &name, DWORD type, const std::vector &data) { std::string left = name.empty() ? "@" : reg_quote(name); if (type == REG_DWORD && data.size() >= 4) { DWORD v = *reinterpret_cast(data.data()); char buf[32]; std::snprintf(buf, sizeof(buf), "dword:%08lx", static_cast(v)); return left + "=" + buf + "\r\n"; } if (type == REG_SZ) return left + "=" + reg_quote(value_to_text(type, data)) + "\r\n"; if (type == REG_EXPAND_SZ) return left + "=hex(2):" + reg_hex_list(data.data(), static_cast(data.size())) + "\r\n"; if (type == REG_MULTI_SZ) return left + "=hex(7):" + reg_hex_list(data.data(), static_cast(data.size())) + "\r\n"; if (type == REG_QWORD) return left + "=hex(b):" + reg_hex_list(data.data(), static_cast(data.size())) + "\r\n"; return left + "=hex:" + reg_hex_list(data.data(), static_cast(data.size())) + "\r\n"; } static bool open_key(const std::string &path, REGSAM access, HKEY &key, RegPath *parsed, std::string &error) { RegPath rp; if (!parse_path(path, rp, error)) return false; LSTATUS st = RegOpenKeyExW(rp.root, rp.subkey.empty() ? nullptr : rp.subkey.c_str(), 0, access, &key); if (st != ERROR_SUCCESS) { error = win_error(st); return false; } if (parsed) *parsed = rp; return true; } static void handle_list_key(const std::string &path) { HKEY key = nullptr; RegPath rp; std::string error; REGSAM list_access = KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS; if (!open_key(path, list_access, key, &rp, error)) { send_event("registry_result", err_payload("list_key", "Cannot open " + path + ": " + error)); return; } DWORD sub_count = 0, max_sub = 0, val_count = 0, max_val = 0, max_data = 0; RegQueryInfoKeyW(key, nullptr, nullptr, nullptr, &sub_count, &max_sub, nullptr, &val_count, &max_val, &max_data, nullptr, nullptr); std::ostringstream subkeys; subkeys << "["; for (DWORD i = 0; i < sub_count; i++) { std::wstring name(max_sub + 2, L'\0'); DWORD len = static_cast(name.size()); if (RegEnumKeyExW(key, i, &name[0], &len, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) { if (i) subkeys << ","; subkeys << "\"" << json_escape(narrow(std::wstring(name.c_str(), len))) << "\""; } } subkeys << "]"; std::ostringstream values; values << "["; bool first = true; for (DWORD i = 0; i < val_count; i++) { std::wstring name(max_val + 2, L'\0'); DWORD name_len = static_cast(name.size()); DWORD type = 0; DWORD data_len = max_data + 2; std::vector data(data_len ? data_len : 2); LSTATUS st = RegEnumValueW(key, i, &name[0], &name_len, nullptr, &type, data.data(), &data_len); if (st == ERROR_MORE_DATA) { data.resize(data_len); name_len = static_cast(name.size()); st = RegEnumValueW(key, i, &name[0], &name_len, nullptr, &type, data.data(), &data_len); } if (st == ERROR_SUCCESS) { data.resize(data_len); if (!first) values << ","; first = false; values << "{\"name\":\"" << json_escape(narrow(std::wstring(name.c_str(), name_len))) << "\",\"type\":\"" << type_name(type) << "\",\"data\":\"" << json_escape(value_to_text(type, data)) << "\"}"; } } values << "]"; RegCloseKey(key); send_event("registry_result", ok_payload("list_key", "{\"path\":\"" + json_escape(full_path(rp)) + "\",\"subkeys\":" + subkeys.str() + ",\"values\":" + values.str() + "}")); } static void handle_create_key(const std::string &path) { RegPath rp; std::string error; if (!parse_path(path, rp, error)) { send_event("registry_result", err_payload("create_key", error)); return; } HKEY key = nullptr; DWORD disp = 0; LSTATUS st = RegCreateKeyExW(rp.root, rp.subkey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, &key, &disp); if (st == ERROR_SUCCESS) { RegCloseKey(key); send_event("registry_result", ok_payload("create_key")); } else { send_event("registry_result", err_payload("create_key", win_error(st))); } } static void handle_delete_key(const std::string &path) { RegPath rp; std::string error; if (!parse_path(path, rp, error)) { send_event("registry_result", err_payload("delete_key", error)); return; } if (rp.subkey.empty()) { send_event("registry_result", err_payload("delete_key", "Root hives cannot be deleted")); return; } LSTATUS st = RegDeleteTreeW(rp.root, rp.subkey.c_str()); if (st == ERROR_SUCCESS) send_event("registry_result", ok_payload("delete_key")); else send_event("registry_result", err_payload("delete_key", win_error(st))); } static void handle_set_value(const char *payload, int payloadLen) { std::string path = json_string(payload, payloadLen, "path"); std::string name = json_string(payload, payloadLen, "name"); std::string old_name = json_string(payload, payloadLen, "oldName"); std::string type_s = json_string(payload, payloadLen, "type"); std::string data_s = json_string(payload, payloadLen, "data"); HKEY key = nullptr; std::string error; if (!open_key(path, KEY_SET_VALUE, key, nullptr, error)) { send_event("registry_result", err_payload("set_value", error)); return; } if (old_name != name && !old_name.empty()) { RegDeleteValueW(key, widen(old_name).c_str()); } DWORD type = type_from_name(type_s); std::vector data; if (!text_to_value(type, data_s, data, error)) { RegCloseKey(key); send_event("registry_result", err_payload("set_value", error)); return; } std::wstring wname = widen(name); LSTATUS st = RegSetValueExW(key, name.empty() ? nullptr : wname.c_str(), 0, type, data.empty() ? nullptr : data.data(), static_cast(data.size())); RegCloseKey(key); if (st == ERROR_SUCCESS) send_event("registry_result", ok_payload("set_value")); else send_event("registry_result", err_payload("set_value", win_error(st))); } static void handle_delete_value(const std::string &path, const std::string &name) { HKEY key = nullptr; std::string error; if (!open_key(path, KEY_SET_VALUE, key, nullptr, error)) { send_event("registry_result", err_payload("delete_value", error)); return; } std::wstring wname = widen(name); LSTATUS st = RegDeleteValueW(key, name.empty() ? nullptr : wname.c_str()); RegCloseKey(key); if (st == ERROR_SUCCESS) send_event("registry_result", ok_payload("delete_value")); else send_event("registry_result", err_payload("delete_value", win_error(st))); } static bool export_key_recursive(const std::string &path, std::string &out, std::string &error) { HKEY key = nullptr; RegPath rp; if (!open_key(path, KEY_READ, key, &rp, error)) return false; out += "\r\n[" + full_path(rp) + "]\r\n"; DWORD sub_count = 0, max_sub = 0, val_count = 0, max_val = 0, max_data = 0; RegQueryInfoKeyW(key, nullptr, nullptr, nullptr, &sub_count, &max_sub, nullptr, &val_count, &max_val, &max_data, nullptr, nullptr); for (DWORD i = 0; i < val_count; i++) { std::wstring name(max_val + 2, L'\0'); DWORD name_len = static_cast(name.size()); DWORD type = 0; DWORD data_len = max_data + 2; std::vector data(data_len ? data_len : 2); if (RegEnumValueW(key, i, &name[0], &name_len, nullptr, &type, data.data(), &data_len) == ERROR_SUCCESS) { data.resize(data_len); out += value_to_reg_line(narrow(std::wstring(name.c_str(), name_len)), type, data); } } std::vector subs; for (DWORD i = 0; i < sub_count; i++) { std::wstring name(max_sub + 2, L'\0'); DWORD len = static_cast(name.size()); if (RegEnumKeyExW(key, i, &name[0], &len, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) { subs.push_back(narrow(std::wstring(name.c_str(), len))); } } RegCloseKey(key); for (const auto &sub : subs) { if (!export_key_recursive(path + "\\" + sub, out, error)) return false; } return true; } static void handle_export_key(const std::string &path) { std::string content = "Windows Registry Editor Version 5.00\r\n"; std::string error; if (!export_key_recursive(path, content, error)) { send_event("registry_result", err_payload("export_key", error)); return; } send_event("registry_result", ok_payload("export_key", "{\"path\":\"" + json_escape(path) + "\",\"content\":\"" + json_escape(content) + "\"}")); } static std::string unquote_reg(const std::string &s) { std::string t = trim(s); if (t.size() >= 2 && t.front() == '"' && t.back() == '"') t = t.substr(1, t.size() - 2); std::string out; for (size_t i = 0; i < t.size(); i++) { if (t[i] == '\\' && i + 1 < t.size()) out += t[++i]; else out += t[i]; } return out; } static bool import_value_line(HKEY key, const std::string &line, std::string &error) { size_t eq = line.find('='); if (eq == std::string::npos) return true; std::string left = trim(line.substr(0, eq)); std::string right = trim(line.substr(eq + 1)); std::string name = left == "@" ? "" : unquote_reg(left); std::wstring wname = widen(name); DWORD type = REG_SZ; std::vector data; if (right == "-") { RegDeleteValueW(key, name.empty() ? nullptr : wname.c_str()); return true; } if (right.rfind("dword:", 0) == 0) { type = REG_DWORD; DWORD v = static_cast(std::strtoul(right.substr(6).c_str(), nullptr, 16)); data.resize(4); std::memcpy(data.data(), &v, 4); } else if (right.rfind("hex(b):", 0) == 0) { type = REG_QWORD; if (!parse_hex_bytes(right.substr(7), data)) { error = "Invalid hex(b) value"; return false; } } else if (right.rfind("hex(7):", 0) == 0) { type = REG_MULTI_SZ; if (!parse_hex_bytes(right.substr(7), data)) { error = "Invalid hex(7) value"; return false; } } else if (right.rfind("hex(2):", 0) == 0) { type = REG_EXPAND_SZ; if (!parse_hex_bytes(right.substr(7), data)) { error = "Invalid hex(2) value"; return false; } } else if (right.rfind("hex:", 0) == 0) { type = REG_BINARY; if (!parse_hex_bytes(right.substr(4), data)) { error = "Invalid hex value"; return false; } } else { type = REG_SZ; std::wstring w = widen(unquote_reg(right)); w.push_back(L'\0'); data.resize(w.size() * sizeof(wchar_t)); std::memcpy(data.data(), w.data(), data.size()); } LSTATUS st = RegSetValueExW(key, name.empty() ? nullptr : wname.c_str(), 0, type, data.empty() ? nullptr : data.data(), static_cast(data.size())); if (st != ERROR_SUCCESS) { error = win_error(st); return false; } return true; } static void handle_import_reg(const std::string &content) { std::stringstream ss(content); std::string raw, current_path, pending; HKEY current = nullptr; int changed = 0; std::string error; auto close_current = [&]() { if (current) RegCloseKey(current); current = nullptr; }; while (std::getline(ss, raw)) { if (!raw.empty() && raw.back() == '\r') raw.pop_back(); std::string line = trim(raw); if (line.empty() || line[0] == ';') continue; if (!line.empty() && line.back() == '\\') { pending += line.substr(0, line.size() - 1); continue; } if (!pending.empty()) { line = pending + line; pending.clear(); } if (line.rfind("Windows Registry Editor", 0) == 0 || line.rfind("REGEDIT4", 0) == 0) continue; if (line.front() == '[' && line.back() == ']') { close_current(); current_path = line.substr(1, line.size() - 2); if (!current_path.empty() && current_path[0] == '-') { RegPath del; if (parse_path(current_path.substr(1), del, error) && !del.subkey.empty()) { RegDeleteTreeW(del.root, del.subkey.c_str()); changed++; } current_path.clear(); continue; } RegPath rp; if (!parse_path(current_path, rp, error)) break; LSTATUS st = RegCreateKeyExW(rp.root, rp.subkey.c_str(), 0, nullptr, 0, KEY_SET_VALUE, nullptr, ¤t, nullptr); if (st != ERROR_SUCCESS) { error = win_error(st); break; } changed++; continue; } if (current) { if (!import_value_line(current, line, error)) break; changed++; } } close_current(); if (!error.empty()) send_event("registry_result", err_payload("import_reg", error)); else send_event("registry_result", ok_payload("import_reg", "{\"changed\":" + std::to_string(changed) + "}")); } #endif EXPORT const char *PluginGetRuntime() { return "cpp"; } #ifdef _WIN32 EXPORT void PluginSetCallback(uint64_t cb) { std::lock_guard lk(g_mu); g_callback = reinterpret_cast(static_cast(cb)); } EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen, uint64_t cb) { (void)hostInfo; (void)hostInfoLen; { std::lock_guard lk(g_mu); g_callback = reinterpret_cast(static_cast(cb)); } #else EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen, uintptr_t cb, uintptr_t ctx) { (void)hostInfo; (void)hostInfoLen; { std::lock_guard lk(g_mu); g_callback = reinterpret_cast(cb); g_callback_ctx = ctx; } #endif send_event("ready", "{\"message\":\"registry editor plugin ready\"}"); return 0; } EXPORT int PluginOnEvent(const char *event, int eventLen, const char *payload, int payloadLen) { std::string ev(event ? event : "", eventLen > 0 ? static_cast(eventLen) : 0u); #ifndef _WIN32 (void)payload; (void)payloadLen; send_event("registry_result", err_payload(ev, "Registry editor is Windows-only")); return 0; #else if (ev == "list_key") handle_list_key(json_string(payload, payloadLen, "path")); else if (ev == "create_key") handle_create_key(json_string(payload, payloadLen, "path")); else if (ev == "delete_key") handle_delete_key(json_string(payload, payloadLen, "path")); else if (ev == "set_value") handle_set_value(payload, payloadLen); else if (ev == "delete_value") handle_delete_value(json_string(payload, payloadLen, "path"), json_string(payload, payloadLen, "name")); else if (ev == "export_key") handle_export_key(json_string(payload, payloadLen, "path")); else if (ev == "import_reg") handle_import_reg(json_string(payload, payloadLen, "content")); else send_event("registry_result", err_payload(ev, "Unknown event")); return 0; #endif } EXPORT void PluginOnUnload() { std::lock_guard lk(g_mu); g_callback = nullptr; #ifndef _WIN32 g_callback_ctx = 0; #endif }