From 75d17beab6f3e09b450b90205f99460de721e4e3 Mon Sep 17 00:00:00 2001 From: johannes Date: Wed, 23 Jul 2025 23:59:51 +0200 Subject: [PATCH] Rudimentary Linux support --- CMakeLists.txt | 9 +- src/Helpers.hpp | 3 +- src/Process.cpp | 2 + src/Process.hpp | 4 +- src/ReGenny.cpp | 34 +++++++- src/arch/Arch.cpp | 6 ++ src/arch/Linux.cpp | 164 +++++++++++++++++++++++++++++++++++++ src/arch/Linux.hpp | 38 +++++++++ third_party/CMakeLists.txt | 12 ++- 9 files changed, 262 insertions(+), 10 deletions(-) create mode 100644 src/arch/Linux.cpp create mode 100644 src/arch/Linux.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 901c351..85868ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,13 @@ add_subdirectory(third_party) # regenny # file(GLOB_RECURSE regenny_sources "src/*") + +if(MSVC) + list(FILTER regenny_sources EXCLUDE REGEX "src/arch/Linux.cpp$") +else() + list(FILTER regenny_sources EXCLUDE REGEX "src/arch/Windows.cpp$") +endif() + add_executable(regenny ${regenny_sources}) target_include_directories(regenny PRIVATE "src") target_link_libraries(regenny PRIVATE @@ -27,4 +34,4 @@ target_link_libraries(regenny PRIVATE lua sol2::sol2 luagenny -) \ No newline at end of file +) diff --git a/src/Helpers.hpp b/src/Helpers.hpp index dbae131..45a3449 100644 --- a/src/Helpers.hpp +++ b/src/Helpers.hpp @@ -6,5 +6,6 @@ class Helpers { public: + virtual ~Helpers() = default; virtual std::map processes() = 0; -}; \ No newline at end of file +}; diff --git a/src/Process.cpp b/src/Process.cpp index 79866fe..34e1b42 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -1,5 +1,7 @@ #include "Process.hpp" +#include + bool Process::read(uintptr_t address, void* buffer, size_t size) { // If we're reading from read-only memory we can just use the cached version since it hasn't changed. for (auto&& ro_allocation : m_read_only_allocations) { diff --git a/src/Process.hpp b/src/Process.hpp index 43cba78..6a38ede 100644 --- a/src/Process.hpp +++ b/src/Process.hpp @@ -7,6 +7,8 @@ class Process { public: + virtual ~Process() = default; + class Module { public: std::string name{}; @@ -74,4 +76,4 @@ class Process { virtual std::optional handle_allocate(uintptr_t address, size_t size, uint64_t flags) { return std::nullopt; } -}; \ No newline at end of file +}; diff --git a/src/ReGenny.cpp b/src/ReGenny.cpp index f948d8f..748c82c 100644 --- a/src/ReGenny.cpp +++ b/src/ReGenny.cpp @@ -3,7 +3,10 @@ #include #include +#if __has_include() #include +#define REGENNY_HAS_PPL +#endif #include #include @@ -1021,8 +1024,7 @@ void ReGenny::rtti_sweep_ui() { std::vector base_data(m_type->size()); m_process->read(m_address, base_data.data(), base_data.size()); - // for (size_t i = 0; i < base_data.size(); i += sizeof(void*)) { - concurrency::parallel_for(size_t{0}, base_data.size(), size_t{sizeof(void*)}, [&](size_t i) { + static const auto process_data = [&](std::size_t i) { if (i + sizeof(void*) >= base_data.size()) { return; } @@ -1043,7 +1045,19 @@ void ReGenny::rtti_sweep_ui() { std::scoped_lock _{m_ui.rtti_lock}; m_ui.rtti_sweep_text += fmt::format("struct {:s}* @ 0x{:x}\n", *tname, (uintptr_t)i); } + }; + +#ifdef REGENNY_HAS_PPL + concurrency::parallel_for(size_t{0}, base_data.size(), size_t{sizeof(void*)}, [&](size_t i) { +#else + for (size_t i = 0; i < base_data.size(); i += sizeof(void*)) { +#endif + process_data(i); +#ifdef REGENNY_HAS_PPL }); +#else + } +#endif struct Chain { uintptr_t base; @@ -1073,8 +1087,7 @@ void ReGenny::rtti_sweep_ui() { std::recursive_mutex local_mutex{}; - // for (size_t i = 0; i < data.size(); i += sizeof(void*)) { - concurrency::parallel_for(size_t{0}, data.size(), size_t{sizeof(void*)}, [&](size_t i) { + static const auto process_data = [&](std::size_t i) { if (i + sizeof(void*) >= data.size()) { return; } @@ -1106,7 +1119,19 @@ void ReGenny::rtti_sweep_ui() { std::scoped_lock _{local_mutex}; result.insert(result.end(), new_results.begin(), new_results.end()); } + }; + +#ifdef REGENNY_HAS_PPL + concurrency::parallel_for(size_t{0}, data.size(), size_t{sizeof(void*)}, [&](size_t i) { +#else + for (size_t i = 0; i < data.size(); i += sizeof(void*)) { +#endif + process_data(i); +#ifdef REGENNY_HAS_PPL }); +#else + } +#endif // std::sort(result.begin(), result.end(), [](auto&& a, auto&& b) { return a.offset < b.offset; }); @@ -1311,6 +1336,7 @@ void ReGenny::memory_ui() { void ReGenny::set_address() { for (auto address : query_address_resolvers(m_ui.address)) { auto addr_str = fmt::format("0x{:x}", address); + std::cout << addr_str << std::endl; if (auto addr = parse_address(addr_str)) { m_parsed_address = *addr; } diff --git a/src/arch/Arch.cpp b/src/arch/Arch.cpp index 4edc5bd..0c8a96d 100644 --- a/src/arch/Arch.cpp +++ b/src/arch/Arch.cpp @@ -1,5 +1,7 @@ #ifdef _WIN32 #include "Windows.hpp" +#else +#include "Linux.hpp" #endif #include "Arch.hpp" @@ -7,11 +9,15 @@ std::unique_ptr arch::make_helpers() { #ifdef _WIN32 return std::make_unique(); +#else + return std::make_unique(); #endif } std::unique_ptr arch::open_process(uint32_t process_id) { #ifdef _WIN32 return std::make_unique(process_id); +#else + return std::make_unique(process_id); #endif } diff --git a/src/arch/Linux.cpp b/src/arch/Linux.cpp new file mode 100644 index 0000000..bcbd6c9 --- /dev/null +++ b/src/arch/Linux.cpp @@ -0,0 +1,164 @@ +#include "Linux.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace arch { + +LinuxProcess::LinuxProcess(pid_t process_id) : m_process(process_id) { + std::string path = "/proc/" + std::to_string(process_id) + "/mem"; + rw_handle = open(path.c_str(), O_RDWR); + assert(rw_handle >= 0); // TODO better error handling + + std::fstream maps{"/proc/" + std::to_string(process_id) + "/maps", std::fstream::in}; + assert(maps); + + for (std::string line; std::getline(maps, line);) { + if (line.empty()) + continue; // ? + + std::uintptr_t begin = 0; + std::uintptr_t end = 0; + std::array perms{}; + char shared = 0; + std::string name; + + int offset = -1; + + sscanf(line.c_str(), "%zx-%zx %c%c%c%c %*x %*x:%*x %*x%n", &begin, &end, &perms[0], &perms[1], &perms[2], + &shared, &offset); + + while (offset < line.length() && line[offset] == ' ') + offset++; + if (offset < line.length()) + name = line.c_str() + offset; + + if (!name.empty()) { + constexpr static const char* DELETED_TAG = " (deleted)"; + constexpr static std::size_t DELETED_TAG_LEN = std::char_traits::length(DELETED_TAG); + if (name.ends_with(DELETED_TAG)) { + name = name.substr(0, name.length() - DELETED_TAG_LEN); + } + + if (name[0] == '[') { + perms[0] = '-'; + } + } + + if (!name.empty()) { + auto it = std::ranges::find_if(m_modules, [&name](Module& module) { return module.name == name; }); + + if (it == m_modules.end()) { + m_modules.emplace_back(name, begin, end, end - begin); + } else { + it->start = std::min(it->start, begin); + it->end = std::min(it->end, end); + it->size = it->end - it->start; + } + } + + m_allocations.emplace_back(begin, end, end - begin, perms[0] == 'r', perms[1] == 'w', perms[2] == 'x'); + + if (perms[0] == 'r') { + ReadOnlyAllocation ro_alloc{ + Allocation(begin, end, end - begin, perms[0] == 'r', perms[1] == 'w', perms[2] == 'x'), {}}; + + ro_alloc.mem.reserve(end - begin); + + if (read(begin, ro_alloc.mem.data(), end - begin)) { + m_read_only_allocations.emplace_back(std::move(ro_alloc)); + } + } + } + + maps.close(); +} + +uint32_t LinuxProcess::process_id() { + return m_process; +} + +bool LinuxProcess::ok() { + return kill(m_process, 0) == 0; +} + +std::optional LinuxProcess::get_typename(uintptr_t ptr) { + // TODO + return {}; +} +std::optional LinuxProcess::get_typename_from_vtable(uintptr_t ptr) { + // TODO + return {}; +} + +bool LinuxProcess::handle_write(uintptr_t address, const void* buffer, size_t size) { + return pwrite(rw_handle, buffer, size, address) == static_cast(size); +} +bool LinuxProcess::handle_read(uintptr_t address, void* buffer, size_t size) { + return pread(rw_handle, buffer, size, address) == static_cast(size); +} +std::optional LinuxProcess::handle_protect(uintptr_t address, size_t size, uint64_t flags) { + // TODO + return {}; +} +std::optional LinuxProcess::handle_allocate(uintptr_t address, size_t size, uint64_t flags) { + // TODO + return {}; +} + +std::map LinuxHelpers::processes() { + std::map processes; + for (const std::filesystem::path& it : std::filesystem::directory_iterator{"/proc"}) { + if (!std::filesystem::is_directory(it)) + continue; + + const std::string& str = it.filename(); + + pid_t pid; + auto result = std::from_chars(str.data(), str.data() + str.size(), pid); + + if (result.ec != std::errc{}) + continue; + + std::ifstream f{"/proc/" + str + "/status"}; + + std::string name; + + for (std::string line; std::getline(f, line);) { + static constexpr std::string_view PATTERN = "Name:"; + + if (line.starts_with(PATTERN)) { + name = line.substr(line.find_first_not_of(' ', PATTERN.length() + 1)); + break; + } + } + + f.close(); + if (name.empty()) + continue; + + processes.insert({pid, name}); + } + return processes; +} + +} // namespace arch diff --git a/src/arch/Linux.hpp b/src/arch/Linux.hpp new file mode 100644 index 0000000..e8bb59b --- /dev/null +++ b/src/arch/Linux.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include "Helpers.hpp" +#include "Process.hpp" + +namespace arch { +class LinuxProcess : public Process { +public: + LinuxProcess(pid_t process_id); + ~LinuxProcess() override = default; + + uint32_t process_id() override; + bool ok() override; + + std::optional get_typename(uintptr_t ptr) override; + std::optional get_typename_from_vtable(uintptr_t ptr) override; + +protected: + bool handle_write(uintptr_t address, const void* buffer, size_t size) override; + bool handle_read(uintptr_t address, void* buffer, size_t size) override; + std::optional handle_protect(uintptr_t address, size_t size, uint64_t flags) override; + std::optional handle_allocate(uintptr_t address, size_t size, uint64_t flags) override; + +private: + pid_t m_process{}; + int rw_handle; +}; + +class LinuxHelpers : public Helpers { +public: + ~LinuxHelpers() override = default; + + std::map processes() override; +}; +} // namespace arch diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 1fefdff..d405e50 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -34,10 +34,15 @@ CPMAddPackage( DOWNLOAD_ONLY YES ) if (nativefiledialog_ADDED) + if(MSVC) + set(NFD_IMPLEMENTATION ${nativefiledialog_SOURCE_DIR}/src/nfd_win.cpp) + else() + set(NFD_IMPLEMENTATION ${nativefiledialog_SOURCE_DIR}/src/nfd_zenity.c) + endif() add_library( nativefiledialog STATIC ${nativefiledialog_SOURCE_DIR}/src/nfd_common.c - ${nativefiledialog_SOURCE_DIR}/src/nfd_win.cpp + ${NFD_IMPLEMENTATION} ) target_include_directories(nativefiledialog PUBLIC $/src/include) endif () @@ -95,6 +100,7 @@ if (lua_ADDED) endif () # sol2 +# TODO Merge this or upgrade to 3.5.0 when it comes out https://github.com/ThePhD/sol2/commit/d805d027e0a0a7222e936926139f06e23828ce9f CPMAddPackage("gh:ThePhD/sol2@3.3.0") # luagenny @@ -112,7 +118,7 @@ if (luagenny_ADDED) if (MSVC) target_compile_options(luagenny PUBLIC /bigobj) else () - target_compile_options(luagenny PRIVATE -fdeclspec -Wno-ignored-attributes) + target_compile_options(luagenny PRIVATE -Wno-ignored-attributes) endif () endif () @@ -125,6 +131,6 @@ add_library(scope_guard INTERFACE) target_include_directories(scope_guard INTERFACE "scope_guard") # SDL_Trigger -add_library(SDL_Trigger STATIC "SDL_Trigger/SDL_Trigger.cpp") +add_library(SDL_Trigger STATIC "SDL_Trigger/sdl_trigger.cpp") target_include_directories(SDL_Trigger INTERFACE "SDL_Trigger") target_link_libraries(SDL_Trigger PUBLIC SDL3::SDL3)