From d211211ac87d30e4774c01e6765c2419dce3fe1f Mon Sep 17 00:00:00 2001 From: Josh Ferrell Date: Fri, 10 Nov 2023 17:52:21 -0500 Subject: Add support for object settings --- binaryninjaapi.h | 42 ++++++++++++++++++++++-------------------- binaryninjacore.h | 6 ++++++ python/settings.py | 40 ++++++++++++++++++++++++++++++++++++++++ settings.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ ui/settingsview.h | 4 ++++ 5 files changed, 115 insertions(+), 20 deletions(-) diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 99eb1344..485a01d6 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -14727,26 +14727,26 @@ namespace BinaryNinja { \c RegisterGroup method allows for specifying a UI friendly title for use in the Binary Ninja UI. Defining a new setting requires a unique setting key and a JSON string of property, value pairs. The following table describes the available properties and values. - ================== ====================================== ================== ======== ======================================================================= - Property JSON Data Type Prerequisite Optional {Allowed Values} and Notes - ================== ====================================== ================== ======== ======================================================================= - "title" string None No Concise Setting Title - "type" string None No {"array", "boolean", "number", "password", "string"} - "elementType" string "type" is "array" No {"string"} - "enum" array : {string} "type" is "array" Yes Enumeration definitions - "enumDescriptions" array : {string} "type" is "array" Yes Enumeration descriptions that match "enum" array - "minValue" number "type" is "number" Yes Specify 0 to infer unsigned (default is signed) - "maxValue" number "type" is "number" Yes Values less than or equal to INT_MAX result in a QSpinBox UI element - "precision" number "type" is "number" Yes Specify precision for a QDoubleSpinBox - "default" {array, boolean, number, string, null} None Yes Specify optimal default value - "aliases" array : {string} None Yes Array of deprecated setting key(s) - "description" string None No Detailed setting description - "ignore" array : {string} None Yes {"SettingsUserScope", "SettingsProjectScope", "SettingsResourceScope"} - "message" string None Yes An optional message with additional emphasis - "readOnly" bool None Yes Only enforced by UI elements - "optional" bool None Yes Indicates setting can be null - "requiresRestart bool None Yes Enable restart notification in the UI upon change - ================== ====================================== ================== ======== ======================================================================= + ================== ====================================== ====================================== ======== ======================================================================= + Property JSON Data Type Prerequisite Optional {Allowed Values} and Notes + ================== ====================================== ====================================== ======== ======================================================================= + "title" string None No Concise Setting Title + "type" string None No {"array", "boolean", "number", "object", "password", "string"} + "elementType" string "type" is "array" or type is "object" No {"string"} + "enum" array : {string} "type" is "array" Yes Enumeration definitions + "enumDescriptions" array : {string} "type" is "array" Yes Enumeration descriptions that match "enum" array + "minValue" number "type" is "number" Yes Specify 0 to infer unsigned (default is signed) + "maxValue" number "type" is "number" Yes Values less than or equal to INT_MAX result in a QSpinBox UI element + "precision" number "type" is "number" Yes Specify precision for a QDoubleSpinBox + "default" {array, boolean, number, string, null} None Yes Specify optimal default value + "aliases" array : {string} None Yes Array of deprecated setting key(s) + "description" string None No Detailed setting description + "ignore" array : {string} None Yes {"SettingsUserScope", "SettingsProjectScope", "SettingsResourceScope"} + "message" string None Yes An optional message with additional emphasis + "readOnly" bool None Yes Only enforced by UI elements + "optional" bool None Yes Indicates setting can be null + "requiresRestart bool None Yes Enable restart notification in the UI upon change + ================== ====================================== ====================================== ======== ======================================================================= \note In order to facilitate deterministic analysis results, settings from the default schema that impact analysis are serialized from Default, User, and Project scope into Resource scope during initial BinaryView analysis. This allows an analysis database to be opened @@ -14924,6 +14924,8 @@ namespace BinaryNinja { BNSettingsScope scope = SettingsAutoScope); bool Set(const std::string& key, const std::vector& value, Ref view = nullptr, BNSettingsScope scope = SettingsAutoScope); + bool Set(const std::string& key, const std::map& value, Ref view = nullptr, + BNSettingsScope scope = SettingsAutoScope); bool SetJson(const std::string& key, const std::string& value, Ref view = nullptr, BNSettingsScope scope = SettingsAutoScope); }; diff --git a/binaryninjacore.h b/binaryninjacore.h index 995f2f4f..30e34b81 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -3021,6 +3021,7 @@ extern "C" BINARYNINJACOREAPI void BNFreeString(char* str); BINARYNINJACOREAPI char** BNAllocStringList(const char** contents, size_t size); BINARYNINJACOREAPI void BNFreeStringList(char** strs, size_t count); + BINARYNINJACOREAPI void BNFreeStringObject(char*** obj, size_t count); BINARYNINJACOREAPI void BNShutdown(void); BINARYNINJACOREAPI bool BNIsShutdownRequested(void); @@ -6370,6 +6371,9 @@ extern "C" BNSettings* settings, const char* key, BNBinaryView* view, BNSettingsScope* scope); BINARYNINJACOREAPI const char** BNSettingsGetStringList( BNSettings* settings, const char* key, BNBinaryView* view, BNSettingsScope* scope, size_t* inoutSize); + BINARYNINJACOREAPI const char*** BNSettingsGetStringObject(BNSettings* settings, + const char* key, BNBinaryView* view, BNSettingsScope* scope, size_t* entryCount); + BINARYNINJACOREAPI char* BNSettingsGetJson( BNSettings* settings, const char* key, BNBinaryView* view, BNSettingsScope* scope); @@ -6385,6 +6389,8 @@ extern "C" BNSettings* settings, BNBinaryView* view, BNSettingsScope scope, const char* key, const char* value); BINARYNINJACOREAPI bool BNSettingsSetStringList(BNSettings* settings, BNBinaryView* view, BNSettingsScope scope, const char* key, const char** value, size_t size); + BINARYNINJACOREAPI bool BNSettingsSetStringObject(BNSettings* settings, BNBinaryView* view, BNSettingsScope scope, + const char* key, const char*** value, size_t size); BINARYNINJACOREAPI bool BNSettingsSetJson( BNSettings* settings, BNBinaryView* view, BNSettingsScope scope, const char* key, const char* value); diff --git a/python/settings.py b/python/settings.py index fee8a755..f7bee686 100644 --- a/python/settings.py +++ b/python/settings.py @@ -20,6 +20,8 @@ import ctypes +from typing import Dict + # Binary Ninja components from . import _binaryninjacore as core from .enums import SettingsScope @@ -333,6 +335,18 @@ class Settings: core.BNFreeStringList(result, length) return out_list + def get_string_object(self, key, view=None): + if view is not None: + view = view.handle + length = ctypes.c_ulonglong() + result = core.BNSettingsGetStringObject(self.handle, key, view, None, ctypes.byref(length)) + assert result is not None, "core.BNSettingsGetStringObject returned None" + out_dict = {} + for i in range(length.value): + out_dict[result[i][0].decode('utf8')] = (result[i][1].decode('utf8')) + core.BNFreeStringObject(result, length) + return out_dict + def get_json(self, key, view=None): if view is not None: view = view.handle @@ -379,6 +393,19 @@ class Settings: core.BNFreeStringList(result, length) return (out_list, SettingsScope(c_scope.value)) + def get_string_object_with_scope(self, key, view=None, scope=SettingsScope.SettingsAutoScope): + if view is not None: + view = view.handle + c_scope = core.SettingsScopeEnum(scope) + length = ctypes.c_ulonglong() + result = core.BNSettingsGetStringObject(self.handle, key, view, ctypes.byref(c_scope), ctypes.byref(length)) + assert result is not None, "core.BNSettingsGetStringObject returned None" + out_dict = {} + for i in range(length.value): + out_dict[result[i][0].decode('utf8')] = (result[i][1].decode('utf8')) + core.BNFreeStringObject(result, length) + return (out_dict, SettingsScope(c_scope.value)) + def get_json_with_scope(self, key, view=None, scope=SettingsScope.SettingsAutoScope): if view is not None: view = view.handle @@ -416,6 +443,19 @@ class Settings: string_list[i] = value[i].encode('charmap') return core.BNSettingsSetStringList(self.handle, view, scope, key, string_list, length) + def set_string_object(self, key, value: Dict[str, str], view=None, scope=SettingsScope.SettingsAutoScope): + if view is not None: + view = view.handle + length = ctypes.c_ulonglong() + length.value = len(value) + entry_val = (ctypes.c_char_p * 2) + string_obj = (ctypes.POINTER(ctypes.c_char_p) * len(value))() + for i,entry in enumerate(value.items()): + string_obj[i] = entry_val() + string_obj[i][0] = entry[0].encode('charmap') + string_obj[i][1] = entry[1].encode('charmap') + return core.BNSettingsSetStringObject(self.handle, view, scope, key, string_obj, length) + def set_json(self, key, value, view=None, scope=SettingsScope.SettingsAutoScope): if view is not None: view = view.handle diff --git a/settings.cpp b/settings.cpp index 59394c42..6066aa1e 100644 --- a/settings.cpp +++ b/settings.cpp @@ -259,6 +259,26 @@ vector Settings::Get>(const string& key, Ref } +template <> +map Settings::Get>(const string& key, Ref view, BNSettingsScope* scope) +{ + // [[key, val], ...] + size_t size = 0; + char*** outBuffer = + (char***)BNSettingsGetStringObject(m_object, key.c_str(), view ? view->GetObject() : nullptr, scope, &size); + + map result; + for (size_t i = 0; i < size; i++) + { + char** entry = outBuffer[i]; + result[entry[0]] = entry[1]; + } + + BNFreeStringObject(outBuffer, size); + return result; +} + + string Settings::GetJson(const string& key, Ref view, BNSettingsScope* scope) { char* tmpStr = BNSettingsGetJson(m_object, key.c_str(), view ? view->GetObject() : nullptr, scope); @@ -329,6 +349,29 @@ bool Settings::Set(const string& key, const vector& value, Ref& value, Ref view, BNSettingsScope scope) +{ + char*** buffer = new char**[value.size()]; + if (!buffer) + return false; + + size_t i = 0; + for (const auto& entry : value) + { + buffer[i] = new char*[2]; + buffer[i][0] = BNAllocString(entry.first.c_str()); + buffer[i][1] = BNAllocString(entry.second.c_str()); + i++; + } + + bool result = BNSettingsSetStringObject( + m_object, view ? view->GetObject() : nullptr, scope, key.c_str(), (const char***)buffer, value.size()); + + BNFreeStringObject(buffer, value.size()); + return result; +} + + bool Settings::SetJson(const string& key, const string& value, Ref view, BNSettingsScope scope) { return BNSettingsSetJson(m_object, view ? view->GetObject() : nullptr, scope, key.c_str(), value.c_str()); diff --git a/ui/settingsview.h b/ui/settingsview.h index df6e19cb..a810af9e 100644 --- a/ui/settingsview.h +++ b/ui/settingsview.h @@ -14,8 +14,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -160,6 +162,7 @@ class BINARYNINJAUIAPI SettingsEditor : public QWidget QSpinBox* m_spinBox = nullptr; QComboBox* m_comboBox = nullptr; QLineEdit* m_arrayText = nullptr; + QTableWidget* m_objectTable = nullptr; std::set m_validComboSelections; Json::StreamWriterBuilder m_builder; @@ -198,6 +201,7 @@ class BINARYNINJAUIAPI SettingsEditor : public QWidget void updateDoubleNumberSetting(double value); void updateIntNumberSetting(int value); void updateArraySetting(); + void updateObjectSetting(); void addArrayStringSetting(const QString& text); void resetSetting(); void resetAllSettings(BNSettingsScope scope); -- cgit v1.3.1