From 1596eaf31a67fdfd8a6f7e9de39d0d9ad113d267 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 02:19:32 -0500 Subject: [PATCH 01/17] Add rvalue overloads for `hat::result` --- include/libhat/result.hpp | 40 +++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/include/libhat/result.hpp b/include/libhat/result.hpp index cc0812d..33d60f1 100644 --- a/include/libhat/result.hpp +++ b/include/libhat/result.hpp @@ -98,7 +98,7 @@ LIBHAT_EXPORT namespace hat { #endif } - LIBHAT_CONSTEXPR_RESULT T& value() { + LIBHAT_CONSTEXPR_RESULT T& value() & { #ifdef LIBHAT_RESULT_EXPECTED return impl.value(); #else @@ -106,7 +106,15 @@ LIBHAT_EXPORT namespace hat { #endif } - LIBHAT_CONSTEXPR_RESULT const T& value() const { + LIBHAT_CONSTEXPR_RESULT T&& value() && { +#ifdef LIBHAT_RESULT_EXPECTED + return std::move(impl).value(); +#else + return std::get<0>(std::move(impl)); +#endif + } + + LIBHAT_CONSTEXPR_RESULT const T& value() const& { #ifdef LIBHAT_RESULT_EXPECTED return impl.value(); #else @@ -114,7 +122,15 @@ LIBHAT_EXPORT namespace hat { #endif } - LIBHAT_CONSTEXPR_RESULT E& error() { + LIBHAT_CONSTEXPR_RESULT const T&& value() const&& { +#ifdef LIBHAT_RESULT_EXPECTED + return std::move(impl).value(); +#else + return std::get<0>(std::move(impl)); +#endif + } + + LIBHAT_CONSTEXPR_RESULT E& error() & { #ifdef LIBHAT_RESULT_EXPECTED return impl.error(); #else @@ -122,11 +138,27 @@ LIBHAT_EXPORT namespace hat { #endif } - LIBHAT_CONSTEXPR_RESULT const E& error() const { + LIBHAT_CONSTEXPR_RESULT E&& error() && { +#ifdef LIBHAT_RESULT_EXPECTED + return std::move(impl).error(); +#else + return std::get<1>(std::move(impl)); +#endif + } + + LIBHAT_CONSTEXPR_RESULT const E& error() const& { #ifdef LIBHAT_RESULT_EXPECTED return impl.error(); #else return std::get<1>(impl); +#endif + } + + LIBHAT_CONSTEXPR_RESULT const E&& error() const&& { +#ifdef LIBHAT_RESULT_EXPECTED + return std::move(impl).error(); +#else + return std::get<1>(std::move(impl)); #endif } }; From 4f87120c15b97c8859baedbd314c0e2bdf73040d Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 02:37:58 -0500 Subject: [PATCH 02/17] Initial overhaul to C bindings --- .../java/me/zero/libhat/ProcessModule.java | 18 +++- include/libhat/c/libhat.h | 54 +++++------ src/c/libhat.cpp | 96 +++++++++---------- 3 files changed, 88 insertions(+), 80 deletions(-) diff --git a/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java b/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java index ae83071..15d124f 100644 --- a/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java +++ b/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java @@ -1,17 +1,29 @@ package me.zero.libhat; import com.sun.jna.Pointer; +import me.zero.libhat.jna.Libhat; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; /** * @author Brady */ -public final class ProcessModule { +public final class ProcessModule implements AutoCloseable { - @NotNull + @Nullable Pointer handle; ProcessModule(@NotNull final Pointer handle) { - this.handle = handle; + this.handle = Objects.requireNonNull(handle); + } + + @Override + public void close() { + if (this.handle != Pointer.NULL) { + Libhat.INSTANCE.libhat_free(this.handle); + this.handle = Pointer.NULL; + } } } diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index bcaa44c..3a0f51e 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -26,7 +26,7 @@ extern "C" { #endif -typedef enum libhat_status_t { +typedef enum libhat_status { libhat_success, // The operation was successful libhat_err_unknown, libhat_err_sig_missing_masked_byte, @@ -34,46 +34,44 @@ typedef enum libhat_status_t { libhat_err_sig_empty_signature, libhat_err_sig_expected_wildcard, libhat_err_sig_invalid_token_length, -} libhat_status_t; +} libhat_status; -typedef enum scan_alignment { - scan_alignment_x1 = 1, - scan_alignment_x4 = 4, - scan_alignment_x16 = 16, -} scan_alignment_t; +typedef enum libhat_alignment { + libhat_alignment_x1 = 1, + libhat_alignment_x4 = 4, + libhat_alignment_x16 = 16, +} libhat_alignment; -typedef struct signature { - void* data; - size_t count; -} signature_t; +typedef struct libhat_signature libhat_signature; +typedef struct libhat_module libhat_module; -LIBHAT_API libhat_status_t libhat_parse_signature( - const char* signatureStr, - signature_t** signatureOut +LIBHAT_API libhat_status libhat_parse_signature( + const char* signatureStr, + libhat_signature** signatureOut ); -LIBHAT_API libhat_status_t libhat_create_signature( - const char* bytes, - const char* mask, - size_t size, - signature_t** signatureOut +LIBHAT_API libhat_status libhat_create_signature( + const char* bytes, + const char* mask, + size_t size, + libhat_signature** signatureOut ); LIBHAT_API const void* libhat_find_pattern( - const signature_t* signature, - const void* buffer, - size_t size, - scan_alignment_t align + const libhat_signature* signature, + const void* buffer, + size_t size, + libhat_alignment align ); LIBHAT_API const void* libhat_find_pattern_mod( - const signature_t* signature, - const void* module, - const char* section, - scan_alignment_t align + const libhat_signature* signature, + const libhat_module* module, + const char* section, + libhat_alignment align ); -LIBHAT_API const void* libhat_get_module(const char* name); +LIBHAT_API const libhat_module* libhat_get_module(const char* name); LIBHAT_API void libhat_free(void* mem); diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index 3e20022..2352056 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -3,23 +3,35 @@ #include #include -static signature_t* allocate_signature(const hat::signature_view signature) { - const auto bytes = std::as_bytes(signature); - auto* mem = malloc(sizeof(signature_t) + bytes.size()); - auto* sig = static_cast(mem); - sig->data = static_cast(mem) + sizeof(signature_t); - sig->count = signature.size(); - std::memcpy(sig->data, bytes.data(), bytes.size()); - return sig; +namespace { + + struct libhat_ffi_object { + virtual ~libhat_ffi_object() = default; + }; + + template + struct libhat_ffi_wrapper : libhat_ffi_object, T { + + template + explicit libhat_ffi_wrapper(Args&&... args) : T(std::forward(args)...) {} + }; } -static hat::scan_alignment to_cpp_align(const scan_alignment align) { +struct libhat_signature final : libhat_ffi_wrapper { + using libhat_ffi_wrapper::libhat_ffi_wrapper; +}; + +struct libhat_module final : libhat_ffi_wrapper { + using libhat_ffi_wrapper::libhat_ffi_wrapper; +}; + +static hat::scan_alignment to_cpp_align(const libhat_alignment align) { switch (align) { - case scan_alignment_x1: + case libhat_alignment_x1: return hat::scan_alignment::X1; - case scan_alignment_x4: + case libhat_alignment_x4: return hat::scan_alignment::X4; - case scan_alignment_x16: + case libhat_alignment_x16: return hat::scan_alignment::X16; } exit(EXIT_FAILURE); @@ -27,7 +39,7 @@ static hat::scan_alignment to_cpp_align(const scan_alignment align) { extern "C" { -LIBHAT_API libhat_status_t libhat_parse_signature(const char* signatureStr, signature_t** signatureOut) { +LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, libhat_signature** signatureOut) { auto result = hat::parse_signature(signatureStr); if (!result.has_value()) { *signatureOut = nullptr; @@ -41,15 +53,15 @@ LIBHAT_API libhat_status_t libhat_parse_signature(const char* signatureStr, sign } return libhat_err_unknown; } - *signatureOut = allocate_signature(result.value()); + *signatureOut = new libhat_signature(std::move(result).value()); return libhat_success; } -LIBHAT_API libhat_status_t libhat_create_signature( - const char* bytes, - const char* mask, - const size_t size, - signature_t** signatureOut +LIBHAT_API libhat_status libhat_create_signature( + const char* bytes, + const char* mask, + const size_t size, + libhat_signature** signatureOut ) { hat::signature signature{}; signature.reserve(size); @@ -60,59 +72,45 @@ LIBHAT_API libhat_status_t libhat_create_signature( signature.emplace_back(std::nullopt); } } - *signatureOut = allocate_signature(signature); + *signatureOut = new libhat_signature(std::move(signature)); return libhat_success; } LIBHAT_API const void* libhat_find_pattern( - const signature_t* signature, - const void* buffer, - const size_t size, - const scan_alignment align + const libhat_signature* signature, + const void* buffer, + const size_t size, + const libhat_alignment align ) { - const hat::signature_view view{ - static_cast(signature->data), - signature->count - }; - const auto begin = static_cast(buffer); const auto end = static_cast(buffer) + size; - const auto result = hat::find_pattern(begin, end, view, to_cpp_align(align)); + const auto result = hat::find_pattern(begin, end, *signature, to_cpp_align(align)); return result.has_result() ? result.get() : nullptr; } LIBHAT_API const void* libhat_find_pattern_mod( - const signature_t* signature, - const void* module, - const char* section, - const scan_alignment align + const libhat_signature* signature, + const libhat_module* module, + const char* section, + const libhat_alignment align ) { - const hat::signature_view view{ - static_cast(signature->data), - signature->count - }; - - const auto mod = hat::process::module_at(const_cast(module)); - if (!mod.has_value()) { - return nullptr; - } - const auto result = hat::find_pattern(view, section, mod.value(), to_cpp_align(align)); + const auto result = hat::find_pattern(*signature, section, *module, to_cpp_align(align)); return result.has_result() ? result.get() : nullptr; } -LIBHAT_API const void* libhat_get_module(const char* name) { +LIBHAT_API const libhat_module* libhat_get_module(const char* name) { if (name) { - if (const auto mod = hat::process::get_module(name); mod.has_value()) { - return reinterpret_cast(mod.value().address()); + if (auto mod = hat::process::get_module(name); mod.has_value()) { + return new libhat_module(std::move(mod).value()); } else { return nullptr; } } - return reinterpret_cast(hat::process::get_process_module().address()); + return new libhat_module(hat::process::get_process_module()); } LIBHAT_API void libhat_free(void* mem) { - free(mem); + delete static_cast(mem); } } // extern "C" From 59a6eb68e4726250b7caca2f7a837738f4ed6bf2 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 02:59:57 -0500 Subject: [PATCH 03/17] More functions --- include/libhat/c/libhat.h | 9 ++++++++- include/libhat/process.hpp | 4 ++-- src/c/libhat.cpp | 36 +++++++++++++++++++++++++----------- src/os/linux/Process.cpp | 2 +- src/os/mac/Process.cpp | 2 +- src/os/win32/Process.cpp | 2 +- 6 files changed, 38 insertions(+), 17 deletions(-) diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index 3a0f51e..299bdec 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -21,6 +21,7 @@ #endif #include +#include #ifdef __cplusplus extern "C" { @@ -71,9 +72,15 @@ LIBHAT_API const void* libhat_find_pattern_mod( libhat_alignment align ); +LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module); + +LIBHAT_API const libhat_module* libhat_get_process_module(); + LIBHAT_API const libhat_module* libhat_get_module(const char* name); -LIBHAT_API void libhat_free(void* mem); +LIBHAT_API const libhat_module* libhat_module_at(const void* address); + +LIBHAT_API void libhat_free(const void* object); #ifdef __cplusplus } diff --git a/include/libhat/process.hpp b/include/libhat/process.hpp index 476c088..03ffddc 100644 --- a/include/libhat/process.hpp +++ b/include/libhat/process.hpp @@ -63,7 +63,7 @@ LIBHAT_EXPORT namespace hat::process { friend hat::process::module get_process_module(); friend std::optional get_module(std::string_view); - friend std::optional module_at(void* address); + friend std::optional module_at(const void* address); std::shared_ptr impl{}; }; @@ -86,5 +86,5 @@ LIBHAT_EXPORT namespace hat::process { /// Returns the module containing the specified address. If the given address is not located within a /// loaded module, std::nullopt is returned instead. - [[nodiscard]] std::optional module_at(void* address); + [[nodiscard]] std::optional module_at(const void* address); } diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index 2352056..a535b2f 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -53,7 +53,7 @@ LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, libhat } return libhat_err_unknown; } - *signatureOut = new libhat_signature(std::move(result).value()); + *signatureOut = new libhat_signature{std::move(result).value()}; return libhat_success; } @@ -72,7 +72,7 @@ LIBHAT_API libhat_status libhat_create_signature( signature.emplace_back(std::nullopt); } } - *signatureOut = new libhat_signature(std::move(signature)); + *signatureOut = new libhat_signature{std::move(signature)}; return libhat_success; } @@ -98,19 +98,33 @@ LIBHAT_API const void* libhat_find_pattern_mod( return result.has_result() ? result.get() : nullptr; } +LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module) { + return module->address(); +} + +LIBHAT_API const libhat_module* libhat_get_process_module() { + return new libhat_module{hat::process::get_process_module()}; +} + LIBHAT_API const libhat_module* libhat_get_module(const char* name) { - if (name) { - if (auto mod = hat::process::get_module(name); mod.has_value()) { - return new libhat_module(std::move(mod).value()); - } else { - return nullptr; - } + if (!name) { + return libhat_get_process_module(); + } + if (auto mod = hat::process::get_module(name); mod.has_value()) { + return new libhat_module{std::move(mod).value()}; + } + return nullptr; +} + +LIBHAT_API const libhat_module* libhat_module_at(const void* address) { + if (auto mod = hat::process::module_at(address); mod.has_value()) { + return new libhat_module{std::move(mod).value()}; } - return new libhat_module(hat::process::get_process_module()); + return nullptr; } -LIBHAT_API void libhat_free(void* mem) { - delete static_cast(mem); +LIBHAT_API void libhat_free(const void* object) { + delete static_cast(object); } } // extern "C" diff --git a/src/os/linux/Process.cpp b/src/os/linux/Process.cpp index 59fcf8e..75a23a0 100644 --- a/src/os/linux/Process.cpp +++ b/src/os/linux/Process.cpp @@ -262,7 +262,7 @@ namespace hat::process { return module; } - std::optional module_at(void* address) { + std::optional module_at(const void* address) { Dl_info dlinfo{}; if (!dladdr(address, &dlinfo)) { return {}; diff --git a/src/os/mac/Process.cpp b/src/os/mac/Process.cpp index 9bbb526..7c3b785 100644 --- a/src/os/mac/Process.cpp +++ b/src/os/mac/Process.cpp @@ -310,7 +310,7 @@ namespace hat::process { return {}; } - std::optional module_at(void* address) { + std::optional module_at(const void* address) { Dl_info dlinfo{}; if (!dladdr(address, &dlinfo)) { return {}; diff --git a/src/os/win32/Process.cpp b/src/os/win32/Process.cpp index c287a11..74a6230 100644 --- a/src/os/win32/Process.cpp +++ b/src/os/win32/Process.cpp @@ -108,7 +108,7 @@ namespace hat::process { return {}; } - std::optional module_at(void* address) { + std::optional module_at(const void* address) { HMODULE out{}; const auto status = GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, From a450a03e0dd429f189973c0192bc5774688e04b7 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 03:18:41 -0500 Subject: [PATCH 04/17] Implement remaining `hat::process::module` member functions --- include/libhat/c/libhat.h | 25 +++++++++++++++++++++++++ src/c/libhat.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index 299bdec..5178878 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -43,9 +43,24 @@ typedef enum libhat_alignment { libhat_alignment_x16 = 16, } libhat_alignment; +typedef enum libhat_protection { + libhat_protection_none = 0b000, + libhat_protection_read = 0b001, + libhat_protection_write = 0b010, + libhat_protection_execute = 0b100, +} libhat_protection; + typedef struct libhat_signature libhat_signature; typedef struct libhat_module libhat_module; +typedef struct libhat_span { + const void* data; + size_t size; +} libhat_span; + +typedef bool(*libhat_for_each_section_cb)(const char* name, libhat_span data, libhat_protection prot, void* user_data); +typedef bool(*libhat_for_each_segment_cb)(libhat_span data, libhat_protection prot, void* user_data); + LIBHAT_API libhat_status libhat_parse_signature( const char* signatureStr, libhat_signature** signatureOut @@ -74,6 +89,16 @@ LIBHAT_API const void* libhat_find_pattern_mod( LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module); +LIBHAT_API libhat_span libhat_module_get_data(const libhat_module* module); + +LIBHAT_API libhat_span libhat_module_get_executable_data(const libhat_module* module); + +LIBHAT_API libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name); + +LIBHAT_API void libhat_module_for_each_section(const libhat_module* module, libhat_for_each_section_cb callback, void* user_data); + +LIBHAT_API void libhat_module_for_each_segment(const libhat_module* module, libhat_for_each_segment_cb callback, void* user_data); + LIBHAT_API const libhat_module* libhat_get_process_module(); LIBHAT_API const libhat_module* libhat_get_module(const char* name); diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index a535b2f..1f5a7ce 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -102,6 +102,35 @@ LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module) { return module->address(); } +LIBHAT_API libhat_span libhat_module_get_data(const libhat_module* module) { + const auto data = module->get_module_data(); + return {data.data(), data.size()}; +} + +LIBHAT_API libhat_span libhat_module_get_executable_data(const libhat_module* module) { + const auto data = module->get_executable_data(); + return {data.data(), data.size()}; +} + +LIBHAT_API libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name) { + const auto data = module->get_section_data(name); + return {data.data(), data.size()}; +} + +LIBHAT_API void libhat_module_for_each_section(const libhat_module* module, const libhat_for_each_section_cb callback, void* user_data) { + std::string buffer; + module->for_each_section([=, &buffer](auto name, auto data, auto prot) { + buffer.assign(name); + return callback(buffer.c_str(), {data.data(), data.size()}, static_cast(prot), user_data); + }); +} + +LIBHAT_API void libhat_module_for_each_segment(const libhat_module* module, const libhat_for_each_segment_cb callback, void* user_data) { + module->for_each_segment([=](auto data, auto prot) { + return callback({data.data(), data.size()}, static_cast(prot), user_data); + }); +} + LIBHAT_API const libhat_module* libhat_get_process_module() { return new libhat_module{hat::process::get_process_module()}; } From b8022bde640839cd894c8d94260575b7222d5b25 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 12:59:00 -0500 Subject: [PATCH 05/17] Missing stdbool --- include/libhat/c/libhat.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index 5178878..07dd237 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -20,6 +20,7 @@ #define LIBHAT_API #endif +#include #include #include From ce7668ffb2f8f36440033ed771fc4f0de37672e8 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 13:00:07 -0500 Subject: [PATCH 06/17] Consting --- include/libhat/c/libhat.h | 24 ++++++++++++++++-------- src/c/libhat.cpp | 10 +++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index 07dd237..3401981 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -63,15 +63,15 @@ typedef bool(*libhat_for_each_section_cb)(const char* name, libhat_span data, li typedef bool(*libhat_for_each_segment_cb)(libhat_span data, libhat_protection prot, void* user_data); LIBHAT_API libhat_status libhat_parse_signature( - const char* signatureStr, - libhat_signature** signatureOut + const char* signatureStr, + const libhat_signature** signatureOut ); LIBHAT_API libhat_status libhat_create_signature( - const char* bytes, - const char* mask, - size_t size, - libhat_signature** signatureOut + const char* bytes, + const char* mask, + size_t size, + const libhat_signature** signatureOut ); LIBHAT_API const void* libhat_find_pattern( @@ -96,9 +96,17 @@ LIBHAT_API libhat_span libhat_module_get_executable_data(const libhat_module* mo LIBHAT_API libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name); -LIBHAT_API void libhat_module_for_each_section(const libhat_module* module, libhat_for_each_section_cb callback, void* user_data); +LIBHAT_API void libhat_module_for_each_section( + const libhat_module* module, + libhat_for_each_section_cb callback, + void* user_data +); -LIBHAT_API void libhat_module_for_each_segment(const libhat_module* module, libhat_for_each_segment_cb callback, void* user_data); +LIBHAT_API void libhat_module_for_each_segment( + const libhat_module* module, + libhat_for_each_segment_cb callback, + void* user_data +); LIBHAT_API const libhat_module* libhat_get_process_module(); diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index 1f5a7ce..dff58dc 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -39,7 +39,7 @@ static hat::scan_alignment to_cpp_align(const libhat_alignment align) { extern "C" { -LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, libhat_signature** signatureOut) { +LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, const libhat_signature** signatureOut) { auto result = hat::parse_signature(signatureStr); if (!result.has_value()) { *signatureOut = nullptr; @@ -58,10 +58,10 @@ LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, libhat } LIBHAT_API libhat_status libhat_create_signature( - const char* bytes, - const char* mask, - const size_t size, - libhat_signature** signatureOut + const char* bytes, + const char* mask, + const size_t size, + const libhat_signature** signatureOut ) { hat::signature signature{}; signature.reserve(size); From a970a2ca65b77d11f9b67a9bec963c7818b92438 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 15:17:31 -0500 Subject: [PATCH 07/17] Add memory protection lookup to C bindings --- include/libhat/c/libhat.h | 6 ++++++ src/c/libhat.cpp | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index 3401981..c1cfc99 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -108,6 +108,12 @@ LIBHAT_API void libhat_module_for_each_segment( void* user_data ); +LIBHAT_API bool libhat_is_readable(const void* data, size_t size); + +LIBHAT_API bool libhat_is_writable(const void* data, size_t size); + +LIBHAT_API bool libhat_is_executable(const void* data, size_t size); + LIBHAT_API const libhat_module* libhat_get_process_module(); LIBHAT_API const libhat_module* libhat_get_module(const char* name); diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index dff58dc..37ee2c8 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -131,6 +131,18 @@ LIBHAT_API void libhat_module_for_each_segment(const libhat_module* module, cons }); } +LIBHAT_API bool libhat_is_readable(const void* data, size_t size) { + return hat::process::is_readable({static_cast(data), size}); +} + +LIBHAT_API bool libhat_is_writable(const void* data, size_t size) { + return hat::process::is_writable({static_cast(data), size}); +} + +LIBHAT_API bool libhat_is_executable(const void* data, size_t size) { + return hat::process::is_executable({static_cast(data), size}); +} + LIBHAT_API const libhat_module* libhat_get_process_module() { return new libhat_module{hat::process::get_process_module()}; } From c0e1959e0f293e188e0fc89cf22041b53128a7d1 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 17:26:04 -0500 Subject: [PATCH 08/17] Update Java bindings --- .../src/main/java/me/zero/libhat/Hat.java | 15 +- .../main/java/me/zero/libhat/jna/Libhat.java | 173 +++++++++++++++--- 2 files changed, 158 insertions(+), 30 deletions(-) diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index bfedafb..cb7f397 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -2,6 +2,7 @@ import com.sun.jna.Native; import com.sun.jna.Pointer; +import com.sun.jna.ptr.PointerByReference; import me.zero.libhat.jna.Libhat; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,12 +43,12 @@ public enum Status { */ public static @NotNull Signature parseSignature(@NotNull final String signature) { Objects.requireNonNull(signature); - final Pointer[] handle = new Pointer[1]; - final int status = Libhat.INSTANCE.libhat_parse_signature(signature, handle); + final PointerByReference out = new PointerByReference(); + final int status = Libhat.INSTANCE.libhat_parse_signature(signature, out); if (status != 0) { throw new RuntimeException("libhat internal error " + Status.values()[status]); } - return new Signature(handle[0]); + return new Signature(out.getValue()); } /** @@ -69,12 +70,12 @@ public enum Status { if (bytes.length != mask.length) { throw new IllegalArgumentException("Mismatch between bytes.length and mask.length"); } - final Pointer[] handle = new Pointer[1]; - final int status = Libhat.INSTANCE.libhat_create_signature(bytes, mask, bytes.length, handle); + final PointerByReference out = new PointerByReference(); + final int status = Libhat.INSTANCE.libhat_create_signature(bytes, mask, new Libhat.size_t(bytes.length), out); if (status != 0) { throw new RuntimeException("libhat internal error " + Status.values()[status]); } - return new Signature(handle[0]); + return new Signature(out.getValue()); } /** @@ -126,7 +127,7 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu final Pointer result = Libhat.INSTANCE.libhat_find_pattern( Objects.requireNonNull(signature.handle), new Pointer(start), - count, + new Libhat.size_t(count), alignment.alignment() ); diff --git a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java index 3040398..fffb472 100644 --- a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java +++ b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java @@ -1,6 +1,7 @@ package me.zero.libhat.jna; import com.sun.jna.*; +import com.sun.jna.ptr.PointerByReference; /** * @author Brady @@ -9,51 +10,177 @@ public interface Libhat extends Library { Libhat INSTANCE = Native.load("libhat_c", Libhat.class); + // ------------------------------------------------------------------------ + // C types + // ------------------------------------------------------------------------ + + class size_t extends IntegerType { + private static final long serialVersionUID = 1L; + + public size_t() { + this(0); + } + + public size_t(long value) { + super(Native.SIZE_T_SIZE, value, true); + } + } + + class uintptr_t extends IntegerType { + private static final long serialVersionUID = 1L; + + public uintptr_t() { + this(0); + } + + public uintptr_t(long value) { + super(Native.POINTER_SIZE, value, true); + } + } + + // ------------------------------------------------------------------------ + // Structures + // ------------------------------------------------------------------------ + + @Structure.FieldOrder({ "data", "size" }) + class Span extends Structure { + public Pointer data; + public size_t size; + + public Span() {} + + public Span(final Pointer p) { + super(p); + read(); + } + + public static class ByValue extends Span implements Structure.ByValue {} + } + + // ------------------------------------------------------------------------ + // Callback types + // ------------------------------------------------------------------------ + + interface ForEachSectionCallback extends Callback { + boolean invoke(String name, Span.ByValue data, int protection, Pointer userData); + } + + interface ForEachSegmentCallback extends Callback { + boolean invoke(Span.ByValue data, int protection, Pointer userData); + } + + // ------------------------------------------------------------------------ + // API + // ------------------------------------------------------------------------ + /* - * libhat_status_t libhat_parse_signature( - * const char* signatureStr, - * signature_t** signatureOut + * libhat_status libhat_parse_signature( + * const char* signatureStr, + * const libhat_signature** signatureOut * ); */ - int libhat_parse_signature(String signatureStr, Pointer[] signatureOut); + int libhat_parse_signature(String signatureStr, PointerByReference signatureOut); /* - * libhat_status_t libhat_create_signature( - * const char* bytes, - * const char* mask, - * size_t size, - * signature_t** signatureOut + * libhat_status libhat_create_signature( + * const char* bytes, + * const char* mask, + * size_t size, + * const libhat_signature** signatureOut * ); */ - int libhat_create_signature(byte[] bytes, byte[] mask, int size, Pointer[] signatureOut); + int libhat_create_signature(byte[] bytes, byte[] mask, size_t size, PointerByReference signatureOut); /* * const void* libhat_find_pattern( - * const signature_t* signature, - * const void* buffer, - * size_t size, - * scan_alignment align + * const libhat_signature* signature, + * const void* buffer, + * size_t size, + * libhat_alignment align * ); */ - Pointer libhat_find_pattern(Pointer signature, Pointer buffer, long size, int align); + Pointer libhat_find_pattern(Pointer signature, Pointer buffer, size_t size, int alignment); /* * const void* libhat_find_pattern_mod( - * const signature_t* signature, - * const void* module, - * const char* section, - * scan_alignment align + * const libhat_signature* signature, + * const libhat_module* module, + * const char* section, + * libhat_alignment align * ); */ - Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int align); + Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int alignment); /* - * const void* libhat_get_module(const char* name); + * uintptr_t libhat_module_address(const libhat_module* module); + */ + uintptr_t libhat_module_address(Pointer module); + + /* + * libhat_span libhat_module_get_data(const libhat_module* module); + */ + Span libhat_module_get_data(Pointer module); + + /* + * libhat_span libhat_module_get_executable_data(const libhat_module* module); + */ + Span libhat_module_get_executable_data(Pointer module); + + /* + * libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name); + */ + Span libhat_module_get_section_data(Pointer module, String name); + + /* + * void libhat_module_for_each_section( + * const libhat_module* module, + * libhat_for_each_section_cb callback, + * void* user_data + * ); + */ + void libhat_module_for_each_section(Pointer module, ForEachSectionCallback callback, Pointer userData); + + /* + * void libhat_module_for_each_segment( + * const libhat_module* module, + * libhat_for_each_segment_cb callback, + * void* user_data + * ); + */ + void libhat_module_for_each_segment(Pointer module, ForEachSegmentCallback callback, Pointer userData); + + /* + * bool libhat_is_readable(const void* data, size_t size); + */ + boolean libhat_is_readable(Pointer data, size_t size); + + /* + * bool libhat_is_writable(const void* data, size_t size); + */ + boolean libhat_is_writable(Pointer data, size_t size); + + /* + * bool libhat_is_executable(const void* data, size_t size); + */ + boolean libhat_is_executable(Pointer data, size_t size); + + /* + * const libhat_module* libhat_get_process_module(); + */ + Pointer libhat_get_process_module(); + + /* + * const libhat_module* libhat_get_module(const char* name); */ Pointer libhat_get_module(String name); /* - * void libhat_free(void* mem); + * const libhat_module* libhat_module_at(const void* address); + */ + Pointer libhat_module_at(Pointer address); + + /* + * void libhat_free(const void* object); */ - void libhat_free(Pointer mem); + void libhat_free(Pointer object); } From 0ceedf24e46485f5048488b3b1838949aded4ac6 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 17:40:49 -0500 Subject: [PATCH 09/17] Add scan hints to C interface --- .../src/main/java/me/zero/libhat/Hat.java | 19 ++++++++---- .../main/java/me/zero/libhat/ScanHint.java | 31 +++++++++++++++++++ .../main/java/me/zero/libhat/jna/Libhat.java | 10 +++--- include/libhat/c/libhat.h | 13 ++++++-- src/c/libhat.cpp | 14 ++++++--- 5 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 bindings/java/src/main/java/me/zero/libhat/ScanHint.java diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index cb7f397..f968dbe 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -101,18 +101,20 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu * range including {@link ByteBuffer#position()} and up to but excluding {@link ByteBuffer#limit()}. If a match is * found, an {@link OptionalInt} containing the absolute position into {@code buffer} is returned. The underlying * memory address of the returned result will be byte aligned per the specified {@link ScanAlignment}. The specified - * {@link ByteBuffer} must be a direct buffer. + * {@link ByteBuffer} must be a direct buffer. Additional hints may be specified to optimize the scan based on known + * properties of the buffer contents. * * @param signature The pattern to match * @param buffer The buffer to search * @param alignment The result address alignment + * @param hints The hints to use * @return The absolute position into {@code buffer} where a match was found, * or {@link OptionalInt#empty()} if there was no match * @throws IllegalArgumentException if the buffer is not direct * @throws NullPointerException if any arguments are {@code null} */ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer, - @NotNull final ScanAlignment alignment) { + @NotNull final ScanAlignment alignment, @NotNull final ScanHint... hints) { Objects.requireNonNull(signature); Objects.requireNonNull(buffer); Objects.requireNonNull(alignment); @@ -128,7 +130,8 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu Objects.requireNonNull(signature.handle), new Pointer(start), new Libhat.size_t(count), - alignment.alignment() + alignment.alignment(), + ScanHint.mask(hints) ); if (result == Pointer.NULL) { @@ -157,16 +160,19 @@ public static Optional findPattern(@NotNull final Signature signature, * Finds the byte pattern described by the given {@link Signature} in the specified {@code section} of the * specified {@code module}. If a match is found, an {@link Optional} containing a Pointer to the match is returned. * The underlying memory address of the returned result will be byte aligned per the specified {@link ScanAlignment}. + * Additional hints may be specified to optimize the scan based on known properties of the buffer contents. * * @param signature The pattern to match * @param module The target module * @param section The section to search in the module * @param alignment The result address alignment + * @param hints The hints to use * @return A pointer to the memory where a match was identified, or {@link Optional#empty()} if none was found. * @throws NullPointerException if any arguments are {@code null} */ public static Optional findPattern(@NotNull final Signature signature, @NotNull final ProcessModule module, - @NotNull final String section, @NotNull final ScanAlignment alignment) { + @NotNull final String section, @NotNull final ScanAlignment alignment, + @NotNull final ScanHint... hints) { Objects.requireNonNull(signature); Objects.requireNonNull(module); Objects.requireNonNull(section); @@ -174,9 +180,10 @@ public static Optional findPattern(@NotNull final Signature signature, final Pointer result = Libhat.INSTANCE.libhat_find_pattern_mod( Objects.requireNonNull(signature.handle), - module.handle, + Objects.requireNonNull(module.handle), section, - alignment.alignment() + alignment.alignment(), + ScanHint.mask(hints) ); return Optional.ofNullable(result); diff --git a/bindings/java/src/main/java/me/zero/libhat/ScanHint.java b/bindings/java/src/main/java/me/zero/libhat/ScanHint.java new file mode 100644 index 0000000..b48e352 --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/ScanHint.java @@ -0,0 +1,31 @@ +package me.zero.libhat; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Brady + */ +public enum ScanHint { + /** + * The data being scanned is x86_64 machine code + */ + X86_64, + + /** + * Only utilize byte pair based scanning if the signature starts with a byte pair + */ + PAIR0, + + /** + * The data being scanned is AArch64 machine code + */ + AARCH64; + + public static int mask(@NotNull ScanHint... hints) { + int mask = 0; + for (final ScanHint hint : hints) { + mask |= (1 << hint.ordinal()); + } + return mask; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java index fffb472..20c5c32 100644 --- a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java +++ b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java @@ -96,20 +96,22 @@ interface ForEachSegmentCallback extends Callback { * const libhat_signature* signature, * const void* buffer, * size_t size, - * libhat_alignment align + * libhat_alignment align, + * libhat_hint hints * ); */ - Pointer libhat_find_pattern(Pointer signature, Pointer buffer, size_t size, int alignment); + Pointer libhat_find_pattern(Pointer signature, Pointer buffer, size_t size, int alignment, int hints); /* * const void* libhat_find_pattern_mod( * const libhat_signature* signature, * const libhat_module* module, * const char* section, - * libhat_alignment align + * libhat_alignment align, + * libhat_hint hints * ); */ - Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int alignment); + Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int alignment, int hints); /* * uintptr_t libhat_module_address(const libhat_module* module); diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index c1cfc99..07ced3d 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -44,6 +44,13 @@ typedef enum libhat_alignment { libhat_alignment_x16 = 16, } libhat_alignment; +typedef enum libhat_hint { + libhat_hint_none = 0, + libhat_hint_x86_64 = 1 << 0, + libhat_hint_pair0 = 1 << 1, + libhat_hint_aarch64 = 1 << 2, +} libhat_hint; + typedef enum libhat_protection { libhat_protection_none = 0b000, libhat_protection_read = 0b001, @@ -78,14 +85,16 @@ LIBHAT_API const void* libhat_find_pattern( const libhat_signature* signature, const void* buffer, size_t size, - libhat_alignment align + libhat_alignment align = libhat_alignment_x1, + libhat_hint hints = libhat_hint_none ); LIBHAT_API const void* libhat_find_pattern_mod( const libhat_signature* signature, const libhat_module* module, const char* section, - libhat_alignment align + libhat_alignment align = libhat_alignment_x1, + libhat_hint hints = libhat_hint_none ); LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module); diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index 37ee2c8..a98997e 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -37,6 +37,10 @@ static hat::scan_alignment to_cpp_align(const libhat_alignment align) { exit(EXIT_FAILURE); } +static hat::scan_hint to_cpp_hints(const libhat_hint hints) { + return static_cast(hints); +} + extern "C" { LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, const libhat_signature** signatureOut) { @@ -80,11 +84,12 @@ LIBHAT_API const void* libhat_find_pattern( const libhat_signature* signature, const void* buffer, const size_t size, - const libhat_alignment align + const libhat_alignment align, + const libhat_hint hints ) { const auto begin = static_cast(buffer); const auto end = static_cast(buffer) + size; - const auto result = hat::find_pattern(begin, end, *signature, to_cpp_align(align)); + const auto result = hat::find_pattern(begin, end, *signature, to_cpp_align(align), to_cpp_hints(hints)); return result.has_result() ? result.get() : nullptr; } @@ -92,9 +97,10 @@ LIBHAT_API const void* libhat_find_pattern_mod( const libhat_signature* signature, const libhat_module* module, const char* section, - const libhat_alignment align + const libhat_alignment align, + const libhat_hint hints ) { - const auto result = hat::find_pattern(*signature, section, *module, to_cpp_align(align)); + const auto result = hat::find_pattern(*signature, section, *module, to_cpp_align(align), to_cpp_hints(hints)); return result.has_result() ? result.get() : nullptr; } From f08f22f281328eb23215476add5c668eb9e9bbb5 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 17:42:48 -0500 Subject: [PATCH 10/17] Crucial parameter name matching --- bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java index 20c5c32..17b5e0a 100644 --- a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java +++ b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java @@ -100,7 +100,7 @@ interface ForEachSegmentCallback extends Callback { * libhat_hint hints * ); */ - Pointer libhat_find_pattern(Pointer signature, Pointer buffer, size_t size, int alignment, int hints); + Pointer libhat_find_pattern(Pointer signature, Pointer buffer, size_t size, int align, int hints); /* * const void* libhat_find_pattern_mod( @@ -111,7 +111,7 @@ interface ForEachSegmentCallback extends Callback { * libhat_hint hints * ); */ - Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int alignment, int hints); + Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int align, int hints); /* * uintptr_t libhat_module_address(const libhat_module* module); From 2d1161c2f39ba2332b114579cd56eb6b123e86ca Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 9 Jul 2026 23:38:59 -0500 Subject: [PATCH 11/17] BIIIIG Java bindings update --- .../src/main/java/me/zero/libhat/Hat.java | 73 ++++++++++++++-- .../java/me/zero/libhat/ProcessModule.java | 87 +++++++++++++++++++ .../main/java/me/zero/libhat/Protection.java | 30 +++++++ .../main/java/me/zero/libhat/ScanHint.java | 10 ++- .../src/main/java/me/zero/libhat/Section.java | 41 +++++++++ .../src/main/java/me/zero/libhat/Segment.java | 33 +++++++ .../main/java/me/zero/libhat/jna/Libhat.java | 17 +++- 7 files changed, 281 insertions(+), 10 deletions(-) create mode 100644 bindings/java/src/main/java/me/zero/libhat/Protection.java create mode 100644 bindings/java/src/main/java/me/zero/libhat/Section.java create mode 100644 bindings/java/src/main/java/me/zero/libhat/Segment.java diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index f968dbe..cf89ef5 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -131,7 +131,7 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu new Pointer(start), new Libhat.size_t(count), alignment.alignment(), - ScanHint.mask(hints) + ScanHint.toFlags(hints) ); if (result == Pointer.NULL) { @@ -183,7 +183,7 @@ public static Optional findPattern(@NotNull final Signature signature, Objects.requireNonNull(module.handle), section, alignment.alignment(), - ScanHint.mask(hints) + ScanHint.toFlags(hints) ); return Optional.ofNullable(result); @@ -204,7 +204,7 @@ public static OptionalInt findPattern(@NotNull final String signature, @NotNull } /** - * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment)} + * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment, ScanHint...)} * * @param signature A byte pattern string * @param buffer The buffer to search @@ -213,10 +213,73 @@ public static OptionalInt findPattern(@NotNull final String signature, @NotNull * @throws NullPointerException if any arguments are {@code null} */ public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer, - @NotNull final ScanAlignment alignment) { + @NotNull final ScanAlignment alignment, @NotNull final ScanHint... hints) { try (final Signature sig = parseSignature(signature)) { - return findPattern(sig, buffer, alignment); + return findPattern(sig, buffer, alignment, hints); + } + } + + /** + * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()} + * and up to but excluding {@link ByteBuffer#limit()}, is readable. The specified {@link ByteBuffer} must be a direct + * buffer. + * + * @param buffer A direct buffer to an arbitrary memory region + * @return Whether the memory is readable + * @throws IllegalArgumentException if the buffer is not direct + * @throws NullPointerException if any arguments are {@code null} + */ + public static boolean isReadable(@NotNull final ByteBuffer buffer) { + Objects.requireNonNull(buffer); + if (!buffer.isDirect()) { + throw new IllegalArgumentException("Provided buffer must be direct"); } + + final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position(); + final int count = buffer.remaining(); + return Libhat.INSTANCE.libhat_is_readable(new Pointer(start), new Libhat.size_t(count)); + } + + /** + * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()} + * and up to but excluding {@link ByteBuffer#limit()}, is writable. The specified {@link ByteBuffer} must be a direct + * buffer. + * + * @param buffer A direct buffer to an arbitrary memory region + * @return Whether the memory is writable + * @throws IllegalArgumentException if the buffer is not direct + * @throws NullPointerException if any arguments are {@code null} + */ + public static boolean isWritable(@NotNull final ByteBuffer buffer) { + Objects.requireNonNull(buffer); + if (!buffer.isDirect()) { + throw new IllegalArgumentException("Provided buffer must be direct"); + } + + final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position(); + final int count = buffer.remaining(); + return Libhat.INSTANCE.libhat_is_writable(new Pointer(start), new Libhat.size_t(count)); + } + + /** + * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()} + * and up to but excluding {@link ByteBuffer#limit()}, is executable. The specified {@link ByteBuffer} must be a direct + * buffer. + * + * @param buffer A direct buffer to an arbitrary memory region + * @return Whether the memory is executable + * @throws IllegalArgumentException if the buffer is not direct + * @throws NullPointerException if any arguments are {@code null} + */ + public static boolean isExecutable(@NotNull final ByteBuffer buffer) { + Objects.requireNonNull(buffer); + if (!buffer.isDirect()) { + throw new IllegalArgumentException("Provided buffer must be direct"); + } + + final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position(); + final int count = buffer.remaining(); + return Libhat.INSTANCE.libhat_is_executable(new Pointer(start), new Libhat.size_t(count)); } /** diff --git a/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java b/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java index 15d124f..aec34d0 100644 --- a/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java +++ b/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java @@ -5,7 +5,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.nio.ByteBuffer; import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; /** * @author Brady @@ -26,4 +29,88 @@ public void close() { this.handle = Pointer.NULL; } } + + /** + * @return The base address of this module, as a {@code long}. + */ + public long getBaseAddress() { + return Libhat.INSTANCE.libhat_module_address(this.checkHandle()).longValue(); + } + + /** + * Returns the complete memory region for this module. This may include portions which are uncommitted. + * To verify whether the region is safe to read, use {@link Hat#isReadable(ByteBuffer)}. + * + * @return The module data + * @throws IllegalStateException If the module data was empty + */ + public @NotNull ByteBuffer getModuleData() { + final ByteBuffer data = Libhat.INSTANCE.libhat_module_get_data(this.checkHandle()).toBuffer(); + if (data == null) { + throw new IllegalStateException("Module data was unexpectedly empty"); + } + return data; + } + + /** + * Returns the executable memory region containing machine code for the module. The standard section which + * contains executable code for the current platform will be returned first. If it cannot be identified + * by name, the first executable region defined by the module will be returned instead. + * + * @return The module's executable data, or {@link Optional#empty()} if it cannot be found. + */ + public @NotNull Optional getExecutableData() { + return Optional.ofNullable(Libhat.INSTANCE.libhat_module_get_executable_data(this.checkHandle()).toBuffer()); + } + + /** + * Returns the memory region for a named section. On Linux-based platforms, section names are lazily loaded + * from the file that initialized the module, and internally cached for subsequent calls. On systems using the + * Mach-O format, "SEGNAME,SECNAME" is supported for disambiguation. i.e. "__TEXT,__const" vs "__DATA,__const" + * + * @param name The section name + * @return The data for the section, or {@link Optional#empty()} if it cannot be found. + * @throws NullPointerException if any arguments are {@code null} + */ + public @NotNull Optional getSectionData(@NotNull final String name) { + Objects.requireNonNull(name); + return Optional.ofNullable(Libhat.INSTANCE.libhat_module_get_section_data(this.checkHandle(), name).toBuffer()); + } + + /** + * Invokes the callback for each named linker section defined by this module as long as it returns true. The + * returned byte range is not guaranteed to have page aligned begin and end addresses. The returned protections + * are yielded from the section headers, and may not reflect the current virtual protections for the relevant + * memory pages. On Linux-based platforms, section names are lazily loaded from the file that initialized the + * module, and internally cached for subsequent calls. + * + * @param callback The callback to accept each section + * @throws NullPointerException if any arguments are {@code null} + */ + public void forEachSection(@NotNull final Predicate<@NotNull Section> callback) { + Objects.requireNonNull(callback); + Libhat.INSTANCE.libhat_module_for_each_section(this.checkHandle(), (name, data, prot, ud) + -> callback.test(new Section(name, data.toBuffer(), Protection.fromFlags(prot))), null); + } + + /** + * Invokes the callback for each memory segment defined by this module as long as it returns true. Depending on + * the platform, a segment may be represented by multiple linker sections. The returned byte range is not + * guaranteed to have page aligned begin and end addresses. The returned protections are yielded from the + * segment headers, and may not reflect the current virtual protections for the relevant memory pages. + * + * @param callback The callback to accept each segment + * @throws NullPointerException if any arguments are {@code null} + */ + public void forEachSegment(@NotNull final Predicate<@NotNull Segment> callback) { + Objects.requireNonNull(callback); + Libhat.INSTANCE.libhat_module_for_each_segment(this.checkHandle(), (data, prot, ud) + -> callback.test(new Segment(data.toBuffer(), Protection.fromFlags(prot))), null); + } + + @NotNull + private Pointer checkHandle() { + return Objects.requireNonNull(this.handle, + "Attempted operation on a ProcessModule that has already been freed"); + } } diff --git a/bindings/java/src/main/java/me/zero/libhat/Protection.java b/bindings/java/src/main/java/me/zero/libhat/Protection.java new file mode 100644 index 0000000..3078f0b --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/Protection.java @@ -0,0 +1,30 @@ +package me.zero.libhat; + +import org.jetbrains.annotations.NotNull; + +import java.util.EnumSet; + +/** + * @author Brady + */ +public enum Protection { + READ, + WRITE, + EXECUTE; + + private final int bit; + + Protection() { + this.bit = (1 << this.ordinal()); + } + + public static @NotNull EnumSet fromFlags(final int flags) { + final EnumSet set = EnumSet.noneOf(Protection.class); + for (final Protection flag : Protection.values()) { + if ((flags & flag.bit) != 0) { + set.add(flag); + } + } + return set; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/ScanHint.java b/bindings/java/src/main/java/me/zero/libhat/ScanHint.java index b48e352..bff6b6b 100644 --- a/bindings/java/src/main/java/me/zero/libhat/ScanHint.java +++ b/bindings/java/src/main/java/me/zero/libhat/ScanHint.java @@ -21,10 +21,16 @@ public enum ScanHint { */ AARCH64; - public static int mask(@NotNull ScanHint... hints) { + private final int bit; + + ScanHint() { + this.bit = (1 << this.ordinal()); + } + + static int toFlags(@NotNull ScanHint... hints) { int mask = 0; for (final ScanHint hint : hints) { - mask |= (1 << hint.ordinal()); + mask |= hint.bit; } return mask; } diff --git a/bindings/java/src/main/java/me/zero/libhat/Section.java b/bindings/java/src/main/java/me/zero/libhat/Section.java new file mode 100644 index 0000000..cbd5dce --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/Section.java @@ -0,0 +1,41 @@ +package me.zero.libhat; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Optional; + +/** + * @author Brady + */ +public final class Section { + + @NotNull + private final String name; + + @Nullable + private final ByteBuffer data; + + @NotNull + private final EnumSet protection; + + public Section(@NotNull final String name, @Nullable final ByteBuffer data, @NotNull final EnumSet protection) { + this.name = name; + this.data = data; + this.protection = protection; + } + + public @NotNull String getName() { + return this.name; + } + + public @NotNull Optional getData() { + return Optional.ofNullable(this.data); + } + + public @NotNull EnumSet getProtection() { + return this.protection; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/Segment.java b/bindings/java/src/main/java/me/zero/libhat/Segment.java new file mode 100644 index 0000000..2f411f2 --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/Segment.java @@ -0,0 +1,33 @@ +package me.zero.libhat; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Optional; + +/** + * @author Brady + */ +public final class Segment { + + @Nullable + private final ByteBuffer data; + + @NotNull + private final EnumSet protection; + + public Segment(@Nullable final ByteBuffer data, @NotNull final EnumSet protection) { + this.data = data; + this.protection = protection; + } + + public @NotNull Optional getData() { + return Optional.ofNullable(data); + } + + public @NotNull EnumSet getProtection() { + return protection; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java index 17b5e0a..cbc61b1 100644 --- a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java +++ b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java @@ -2,6 +2,9 @@ import com.sun.jna.*; import com.sun.jna.ptr.PointerByReference; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; /** * @author Brady @@ -54,6 +57,14 @@ public Span(final Pointer p) { read(); } + @Nullable + public final ByteBuffer toBuffer() { + if (this.data == null || this.size.longValue() == 0) { + return null; + } + return this.data.getByteBuffer(0, this.size.longValue()); + } + public static class ByValue extends Span implements Structure.ByValue {} } @@ -121,17 +132,17 @@ interface ForEachSegmentCallback extends Callback { /* * libhat_span libhat_module_get_data(const libhat_module* module); */ - Span libhat_module_get_data(Pointer module); + Span.ByValue libhat_module_get_data(Pointer module); /* * libhat_span libhat_module_get_executable_data(const libhat_module* module); */ - Span libhat_module_get_executable_data(Pointer module); + Span.ByValue libhat_module_get_executable_data(Pointer module); /* * libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name); */ - Span libhat_module_get_section_data(Pointer module, String name); + Span.ByValue libhat_module_get_section_data(Pointer module, String name); /* * void libhat_module_for_each_section( From f989eb8655343827370d2415d38e3c3bd38cadef Mon Sep 17 00:00:00 2001 From: Brady Date: Fri, 10 Jul 2026 00:01:32 -0500 Subject: [PATCH 12/17] Add disclaimer for current state of C# bindings --- bindings/cs/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 bindings/cs/README.md diff --git a/bindings/cs/README.md b/bindings/cs/README.md new file mode 100644 index 0000000..59f1b0a --- /dev/null +++ b/bindings/cs/README.md @@ -0,0 +1,3 @@ +# libhat-sharp + +These bindings are not up-to-date, but should be compatible with v0.9.0 From 0efc3c6d0a6e4d7c16f4ef09a3d6007d1631d421 Mon Sep 17 00:00:00 2001 From: Brady Date: Fri, 10 Jul 2026 00:11:18 -0500 Subject: [PATCH 13/17] Update status enum --- bindings/java/src/main/java/me/zero/libhat/Hat.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index cf89ef5..3a1a50b 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -21,10 +21,12 @@ private Hat() {} public enum Status { SUCCESS, - ERR_UNKNOWN, - SIG_INVALID, - SIG_EMPTY, - SIG_NO_BYTE + ERROR_UNKNOWN, + ERROR_SIG_MISSING_BYTE, + ERROR_SIG_ELEMENT_PARSE, + ERROR_SIG_EMPTY, + ERROR_SIG_EXPECTED_WILDCARD, + ERROR_SIG_INVALID_TOKEN_LENGTH, } /** From 45feade881281e1655e9ca561584df6796c7d076 Mon Sep 17 00:00:00 2001 From: Brady Date: Fri, 10 Jul 2026 00:21:49 -0500 Subject: [PATCH 14/17] Support bitmask signatures in C API --- bindings/java/src/main/java/me/zero/libhat/Hat.java | 12 ++++++++---- src/c/libhat.cpp | 13 ++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index 3a1a50b..dedb472 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -54,8 +54,8 @@ public enum Status { } /** - * Creates a new {@link Signature} that describes a pattern matching the specified {@code bytes} where the - * corresponding {@code mask} value is non-zero. In other words, a mask value of {@code 0} indicates a wildcard. + * Creates a new {@link Signature} that describes a pattern matching the bits of {@code bytes} where the + * corresponding {@code mask} bit is 1. In other words, where {@code (buffer & mask) == (bytes & mask)}. * The returned {@link Signature} is backed by a native heap allocation, and {@link Signature#close()} must be * called when the object is done being used, either explicitly or through a try-with-resources block. * @@ -285,7 +285,9 @@ public static boolean isExecutable(@NotNull final ByteBuffer buffer) { } /** - * Returns the module for the executable used to create this process + * Returns the module for the executable used to create this process. The returned {@link ProcessModule} is backed + * by a native heap allocation, and {@link ProcessModule#close()} must be called when the object is done being used, + * either explicitly or through a try-with-resources block. * * @return The module * @throws IllegalStateException If the process module could not be retrieved @@ -298,7 +300,9 @@ public static boolean isExecutable(@NotNull final ByteBuffer buffer) { /** * Returns an {@link Optional} containing a handle to the module with the specified name, if such a module exists, * otherwise the returned optional is empty. If the provided name is {@code null}, the module for the executable - * used to create the current process is returned. + * used to create the current process is returned. The returned {@link ProcessModule} is backed by a native heap + * allocation, and {@link ProcessModule#close()} must be called when the object is done being used, either + * explicitly or through a try-with-resources block. * * @param module The module name, may be {@code null} * @return The module diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index a98997e..6a5ee80 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -68,13 +68,16 @@ LIBHAT_API libhat_status libhat_create_signature( const libhat_signature** signatureOut ) { hat::signature signature{}; + bool containsByte = false; signature.reserve(size); for (size_t i{}; i < size; i++) { - if (static_cast(mask[i])) { - signature.emplace_back(static_cast(bytes[i])); - } else { - signature.emplace_back(std::nullopt); - } + containsByte |= signature.emplace_back( + static_cast(bytes[i]), + static_cast(mask[i]) + ).all(); + } + if (!containsByte) { + return libhat_err_sig_missing_masked_byte; } *signatureOut = new libhat_signature{std::move(signature)}; return libhat_success; From 4ce28f8268628c1b5c863154afab6d9b25521877 Mon Sep 17 00:00:00 2001 From: Brady Date: Fri, 10 Jul 2026 01:58:48 -0500 Subject: [PATCH 15/17] Null checks + adjust access --- bindings/java/src/main/java/me/zero/libhat/Protection.java | 2 +- bindings/java/src/main/java/me/zero/libhat/Section.java | 7 ++++--- bindings/java/src/main/java/me/zero/libhat/Segment.java | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bindings/java/src/main/java/me/zero/libhat/Protection.java b/bindings/java/src/main/java/me/zero/libhat/Protection.java index 3078f0b..d1148a7 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Protection.java +++ b/bindings/java/src/main/java/me/zero/libhat/Protection.java @@ -18,7 +18,7 @@ public enum Protection { this.bit = (1 << this.ordinal()); } - public static @NotNull EnumSet fromFlags(final int flags) { + static @NotNull EnumSet fromFlags(final int flags) { final EnumSet set = EnumSet.noneOf(Protection.class); for (final Protection flag : Protection.values()) { if ((flags & flag.bit) != 0) { diff --git a/bindings/java/src/main/java/me/zero/libhat/Section.java b/bindings/java/src/main/java/me/zero/libhat/Section.java index cbd5dce..33febcb 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Section.java +++ b/bindings/java/src/main/java/me/zero/libhat/Section.java @@ -5,6 +5,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; +import java.util.Objects; import java.util.Optional; /** @@ -21,10 +22,10 @@ public final class Section { @NotNull private final EnumSet protection; - public Section(@NotNull final String name, @Nullable final ByteBuffer data, @NotNull final EnumSet protection) { - this.name = name; + Section(@NotNull final String name, @Nullable final ByteBuffer data, @NotNull final EnumSet protection) { + this.name = Objects.requireNonNull(name); this.data = data; - this.protection = protection; + this.protection = Objects.requireNonNull(protection); } public @NotNull String getName() { diff --git a/bindings/java/src/main/java/me/zero/libhat/Segment.java b/bindings/java/src/main/java/me/zero/libhat/Segment.java index 2f411f2..0e7c7e6 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Segment.java +++ b/bindings/java/src/main/java/me/zero/libhat/Segment.java @@ -5,6 +5,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; +import java.util.Objects; import java.util.Optional; /** @@ -18,9 +19,9 @@ public final class Segment { @NotNull private final EnumSet protection; - public Segment(@Nullable final ByteBuffer data, @NotNull final EnumSet protection) { + Segment(@Nullable final ByteBuffer data, @NotNull final EnumSet protection) { this.data = data; - this.protection = protection; + this.protection = Objects.requireNonNull(protection); } public @NotNull Optional getData() { From eb1911535adc5fd5c51b4a7b4c8efd5b05dfdfd2 Mon Sep 17 00:00:00 2001 From: Brady Date: Fri, 10 Jul 2026 02:17:24 -0500 Subject: [PATCH 16/17] More ScanHint overloads --- .../src/main/java/me/zero/libhat/Hat.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index dedb472..5d7b717 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -34,6 +34,7 @@ public enum Status { *
    *
  • 48 8B 8D ? ? ? ? 48
  • *
  • E8 ? ? ? ? 88 86
  • + *
  • FF 15 ? ? ? ? 48 8? ?D
  • *
* The returned {@link Signature} is backed by a native heap allocation, and {@link Signature#close()} must be * called when the object is done being used, either explicitly or through a try-with-resources block. @@ -85,17 +86,20 @@ public enum Status { * range including {@link ByteBuffer#position()} and up to but excluding {@link ByteBuffer#limit()}. If a match is * found, an {@link OptionalInt} containing the absolute position into {@code buffer} is returned. The underlying * memory address of the returned result will not be aligned on any particular boundary. The specified - * {@link ByteBuffer} must be a direct buffer. + * {@link ByteBuffer} must be a direct buffer. Additional hints may be specified to optimize the scan based on + * known properties of the buffer contents. * * @param signature The pattern to match * @param buffer The buffer to search + * @param hints The hints to use * @return The absolute position into {@code buffer} where a match was found, * or {@link OptionalInt#empty()} if there was no match * @throws IllegalArgumentException if the buffer is not direct or the signature has already been closed * @throws NullPointerException if any arguments are {@code null} */ - public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer) { - return findPattern(signature, buffer, ScanAlignment.X1); + public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer, + @NotNull final ScanHint... hints) { + return findPattern(signature, buffer, ScanAlignment.X1, hints); } /** @@ -145,17 +149,19 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu /** * Finds the byte pattern described by the given {@link Signature} in the specified {@code section} of the * specified {@code module}. If a match is found, an {@link Optional} containing a Pointer to the match is returned. - * The underlying memory address of the returned result will not be aligned on any particular boundary. + * The underlying memory address of the returned result will not be aligned on any particular boundary. Additional + * hints may be specified to optimize the scan based on known properties of the buffer contents. * * @param signature The pattern to match * @param module The target module * @param section The section to search in the module + * @param hints The hints to use * @return A pointer to the memory where a match was identified, or {@link Optional#empty()} if none was found. * @throws NullPointerException if any arguments are {@code null} */ public static Optional findPattern(@NotNull final Signature signature, @NotNull final ProcessModule module, - @NotNull final String section) { - return findPattern(signature, module, section, ScanAlignment.X1); + @NotNull final String section, @NotNull final ScanHint... hints) { + return findPattern(signature, module, section, ScanAlignment.X1, hints); } /** @@ -192,25 +198,30 @@ public static Optional findPattern(@NotNull final Signature signature, } /** - * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer)} + * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanHint...)}. + * Additional hints may be specified to optimize the scan based on known properties of the buffer contents. * * @param signature A byte pattern string - * @param buffer The buffer to search + * @param buffer The buffer to search + * @param hints The hints to use * @return The search result * @throws NullPointerException if any arguments are {@code null} */ - public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer) { + public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer, + @NotNull final ScanHint... hints) { try (final Signature sig = parseSignature(signature)) { - return findPattern(sig, buffer); + return findPattern(sig, buffer, hints); } } /** - * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment, ScanHint...)} + * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment, ScanHint...)}. + * Additional hints may be specified to optimize the scan based on known properties of the buffer contents. * * @param signature A byte pattern string - * @param buffer The buffer to search + * @param buffer The buffer to search * @param alignment The memory address alignment of the result + * @param hints The hints to use * @return The search result * @throws NullPointerException if any arguments are {@code null} */ From 16dcffa0835e5fc89ca3a5686a7edc0e57a87edd Mon Sep 17 00:00:00 2001 From: Brady Date: Fri, 10 Jul 2026 02:31:28 -0500 Subject: [PATCH 17/17] Add `libhat_status_to_string` --- .../src/main/java/me/zero/libhat/Hat.java | 14 ++--------- .../java/me/zero/libhat/LibhatException.java | 23 +++++++++++++++++++ .../main/java/me/zero/libhat/jna/Libhat.java | 5 ++++ include/libhat/c/libhat.h | 2 ++ src/c/libhat.cpp | 15 ++++++++++++ 5 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 bindings/java/src/main/java/me/zero/libhat/LibhatException.java diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index 5d7b717..ebf8ce2 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -19,16 +19,6 @@ public final class Hat { private Hat() {} - public enum Status { - SUCCESS, - ERROR_UNKNOWN, - ERROR_SIG_MISSING_BYTE, - ERROR_SIG_ELEMENT_PARSE, - ERROR_SIG_EMPTY, - ERROR_SIG_EXPECTED_WILDCARD, - ERROR_SIG_INVALID_TOKEN_LENGTH, - } - /** * Creates a new {@link Signature} from the specified string representation of a byte pattern. For example: *
    @@ -49,7 +39,7 @@ public enum Status { final PointerByReference out = new PointerByReference(); final int status = Libhat.INSTANCE.libhat_parse_signature(signature, out); if (status != 0) { - throw new RuntimeException("libhat internal error " + Status.values()[status]); + throw new LibhatException(status); } return new Signature(out.getValue()); } @@ -76,7 +66,7 @@ public enum Status { final PointerByReference out = new PointerByReference(); final int status = Libhat.INSTANCE.libhat_create_signature(bytes, mask, new Libhat.size_t(bytes.length), out); if (status != 0) { - throw new RuntimeException("libhat internal error " + Status.values()[status]); + throw new LibhatException(status); } return new Signature(out.getValue()); } diff --git a/bindings/java/src/main/java/me/zero/libhat/LibhatException.java b/bindings/java/src/main/java/me/zero/libhat/LibhatException.java new file mode 100644 index 0000000..9f28283 --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/LibhatException.java @@ -0,0 +1,23 @@ +package me.zero.libhat; + +import me.zero.libhat.jna.Libhat; + +/** + * @author Brady + */ +public class LibhatException extends RuntimeException { + + private final int status; + + LibhatException(final int status) { + super(Libhat.INSTANCE.libhat_status_to_string(status)); + this.status = status; + } + + /** + * @return The actual status code representing the error that occurred + */ + public final int status() { + return this.status; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java index cbc61b1..a4c68af 100644 --- a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java +++ b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java @@ -84,6 +84,11 @@ interface ForEachSegmentCallback extends Callback { // API // ------------------------------------------------------------------------ + /* + * const char* libhat_status_to_string(libhat_status status); + */ + String libhat_status_to_string(int status); + /* * libhat_status libhat_parse_signature( * const char* signatureStr, diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index 07ced3d..4210ae4 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -69,6 +69,8 @@ typedef struct libhat_span { typedef bool(*libhat_for_each_section_cb)(const char* name, libhat_span data, libhat_protection prot, void* user_data); typedef bool(*libhat_for_each_segment_cb)(libhat_span data, libhat_protection prot, void* user_data); +LIBHAT_API const char* libhat_status_to_string(libhat_status status); + LIBHAT_API libhat_status libhat_parse_signature( const char* signatureStr, const libhat_signature** signatureOut diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index 6a5ee80..cc418c3 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -43,6 +43,21 @@ static hat::scan_hint to_cpp_hints(const libhat_hint hints) { extern "C" { +LIBHAT_API const char* libhat_status_to_string(const libhat_status status) { +#define STATUS_CASE(x) case x: return #x + switch (status) { + STATUS_CASE(libhat_success); + STATUS_CASE(libhat_err_unknown); + STATUS_CASE(libhat_err_sig_missing_masked_byte); + STATUS_CASE(libhat_err_sig_element_parse_error); + STATUS_CASE(libhat_err_sig_empty_signature); + STATUS_CASE(libhat_err_sig_expected_wildcard); + STATUS_CASE(libhat_err_sig_invalid_token_length); + } +#undef STATUS_CASE + return "invalid value for libhat_status"; +} + LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, const libhat_signature** signatureOut) { auto result = hat::parse_signature(signatureStr); if (!result.has_value()) {