diff --git a/src/memory/all.hpp b/src/memory/all.hpp index fc3bfd7..adb4754 100644 --- a/src/memory/all.hpp +++ b/src/memory/all.hpp @@ -4,5 +4,5 @@ #include "handle.hpp" #include "module.hpp" #include "pattern.hpp" -#include "pattern_batch.hpp" +#include "batch.hpp" #include "range.hpp" diff --git a/src/memory/batch.cpp b/src/memory/batch.cpp new file mode 100644 index 0000000..e070e58 --- /dev/null +++ b/src/memory/batch.cpp @@ -0,0 +1,78 @@ +#include "batch.hpp" + +#include "common.hpp" +#include "range.hpp" +#include "util/is_enhanced.hpp" + +#include //std::async + +static std::mutex s_entry_mutex; +static std::vector> g_futures; + +namespace memory +{ + void batch::add(std::string name, pattern pattern, std::function callback) + { + m_entries.emplace_back(std::move(name), std::move(pattern), std::move(callback)); + } + void batch::add(std::string name, pattern pattern, int min_version, int max_version, eGameBranch game_branch, std::function callback) + { + m_entries.emplace_back(std::move(name), std::move(pattern), min_version, max_version, game_branch, std::move(callback)); + } + + bool scan_pattern_and_execute_callback(range region, memory::batch::entry entry) + { + if (auto result = region.scan(entry.m_pattern)) + { + if (entry.m_callback) + { + std::lock_guard lock(s_entry_mutex);// Acquire a lock on the mutex to synchronize access. + + std::invoke(std::move(entry.m_callback), *result); + LOG(INFO) << "Found '" << entry.m_name << "' GTA5.exe+" + << HEX_TO_UPPER(result->as() - region.begin().as()); + + return true; + } + } + + LOG(WARNING) << "Failed to find '" << entry.m_name << "'."; + + return false; + } + + bool batch::run(range region) + { + for (auto& entry : m_entries) + { + if (entry.m_game_branch != eGameBranch::DontCare && entry.m_game_branch != big::get_game_branch()) + { + continue; + } + if (entry.m_min_version != -1 && entry.m_min_version > big::g_game_version) // g_game_version is not implemented + { + continue; + } + if (entry.m_max_version != -1 && entry.m_max_version < big::g_game_version) + { + continue; + } + + g_futures.emplace_back(std::async(&scan_pattern_and_execute_callback, region, entry)); + } + + bool found_all_patterns = true; + for (auto& future : g_futures) + { + future.wait(); + + if (!future.get()) + found_all_patterns = false; + } + + m_entries.clear(); + g_futures.clear(); + + return found_all_patterns; + } +} \ No newline at end of file diff --git a/src/memory/pattern_batch.hpp b/src/memory/batch.hpp similarity index 60% rename from src/memory/pattern_batch.hpp rename to src/memory/batch.hpp index 31dbe10..2363c56 100644 --- a/src/memory/pattern_batch.hpp +++ b/src/memory/batch.hpp @@ -1,3 +1,13 @@ +/** + * @file batch.hpp + * + * @copyright GNU General Public License Version 2. + * This file is part of YimMenu. + * YimMenu is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. + * YimMenu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with YimMenu. If not, see . + */ + #pragma once #include "pattern.hpp" @@ -6,15 +16,15 @@ namespace memory { - class pattern_batch + class batch { public: - explicit pattern_batch() = default; - ~pattern_batch() noexcept = default; + explicit batch() = default; + ~batch() noexcept = default; void add(std::string name, pattern pattern, std::function callback); void add(std::string name, pattern pattern, int min_version, int max_version, eGameBranch game_branch, std::function callback); - void run(range region); + bool run(range region); struct entry { diff --git a/src/memory/fwddec.hpp b/src/memory/fwddec.hpp index 8da2a60..1b29f63 100644 --- a/src/memory/fwddec.hpp +++ b/src/memory/fwddec.hpp @@ -6,5 +6,5 @@ namespace memory class range; class module; class pattern; - class pattern_batch; + class batch; } diff --git a/src/memory/handle.hpp b/src/memory/handle.hpp index 8a1df02..d4e6a84 100644 --- a/src/memory/handle.hpp +++ b/src/memory/handle.hpp @@ -1,3 +1,13 @@ +/** + * @file handle.hpp + * + * @copyright GNU General Public License Version 2. + * This file is part of YimMenu. + * YimMenu is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. + * YimMenu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with YimMenu. If not, see . + */ + #pragma once #include #include @@ -12,21 +22,21 @@ namespace memory explicit handle(std::uintptr_t ptr); template - std::enable_if_t, T> as(); + std::enable_if_t, T> as() const; template - std::enable_if_t, T> as(); + std::enable_if_t, T> as() const; template - std::enable_if_t, T> as(); + std::enable_if_t, T> as() const; template - handle add(T offset); + handle add(T offset) const; template - handle sub(T offset); + handle sub(T offset) const; - handle rip(); + handle rip() const; explicit operator bool(); @@ -48,36 +58,36 @@ namespace memory } template - inline std::enable_if_t, T> handle::as() + inline std::enable_if_t, T> handle::as() const { return reinterpret_cast(ptr); } template - inline std::enable_if_t, T> handle::as() + inline std::enable_if_t, T> handle::as() const { return *static_cast>>(ptr); } template - inline std::enable_if_t, T> handle::as() + inline std::enable_if_t, T> handle::as() const { return reinterpret_cast(ptr); } template - inline handle handle::add(T offset) + inline handle handle::add(T offset) const { return handle(as() + offset); } template - inline handle handle::sub(T offset) + inline handle handle::sub(T offset) const { return handle(as() - offset); } - inline handle handle::rip() + inline handle handle::rip() const { return add(as()).add(4); } diff --git a/src/memory/pattern.cpp b/src/memory/pattern.cpp index 7839020..501e645 100644 --- a/src/memory/pattern.cpp +++ b/src/memory/pattern.cpp @@ -1,72 +1,71 @@ +/** + * @file pattern.cpp + * + * @copyright GNU General Public License Version 2. + * This file is part of YimMenu. + * YimMenu is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. + * YimMenu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with YimMenu. If not, see . + */ + #include "pattern.hpp" #include "common.hpp" namespace memory { - pattern::pattern(std::string_view ida_sig) + std::optional to_hex(char const c) { - auto to_upper = [](char c) -> char { - return c >= 'a' && c <= 'z' ? static_cast(c + ('A' - 'a')) : static_cast(c); - }; - - auto to_hex = [&](char c) -> std::optional { - switch (to_upper(c)) - { - case '0': return static_cast(0); - case '1': return static_cast(1); - case '2': return static_cast(2); - case '3': return static_cast(3); - case '4': return static_cast(4); - case '5': return static_cast(5); - case '6': return static_cast(6); - case '7': return static_cast(7); - case '8': return static_cast(8); - case '9': return static_cast(9); - case 'A': return static_cast(10); - case 'B': return static_cast(11); - case 'C': return static_cast(12); - case 'D': return static_cast(13); - case 'E': return static_cast(14); - case 'F': return static_cast(15); - default: return std::nullopt; - } - }; + switch (c) + { + case '0': return static_cast(0x0); + case '1': return static_cast(0x1); + case '2': return static_cast(0x2); + case '3': return static_cast(0x3); + case '4': return static_cast(0x4); + case '5': return static_cast(0x5); + case '6': return static_cast(0x6); + case '7': return static_cast(0x7); + case '8': return static_cast(0x8); + case '9': return static_cast(0x9); + case 'a': return static_cast(0xa); + case 'b': return static_cast(0xb); + case 'c': return static_cast(0xc); + case 'd': return static_cast(0xd); + case 'e': return static_cast(0xe); + case 'f': return static_cast(0xf); + case 'A': return static_cast(0xA); + case 'B': return static_cast(0xB); + case 'C': return static_cast(0xC); + case 'D': return static_cast(0xD); + case 'E': return static_cast(0xE); + case 'F': return static_cast(0xF); + default: return std::nullopt; + } + } - for (std::size_t i = 0; i < ida_sig.size(); ++i) + pattern::pattern(std::string_view ida_sig) + { + const auto size_minus_one = ida_sig.size() - 1; + m_bytes.reserve(size_minus_one / 2); + for (size_t i = 0; i != size_minus_one; ++i) { if (ida_sig[i] == ' ') continue; - bool last = (i == ida_sig.size() - 1); if (ida_sig[i] != '?') { - if (!last) + auto c1 = to_hex(ida_sig[i]); + auto c2 = to_hex(ida_sig[i + 1]); + if (c1 && c2) { - auto c1 = to_hex(ida_sig[i]); - auto c2 = to_hex(ida_sig[i + 1]); - - if (c1 && c2) - { - m_bytes.emplace_back(static_cast((*c1 * 0x10) + *c2)); - } + m_bytes.emplace_back(static_cast((*c1 * 0x10) + *c2)); } } else { - m_bytes.push_back(std::nullopt); + m_bytes.push_back({}); } } } - - pattern::pattern(const void* bytes, std::string_view mask) - { - for (std::size_t i = 0; i < mask.size(); ++i) - { - if (mask[i] != '?') - m_bytes.emplace_back(static_cast(bytes)[i]); - else - m_bytes.push_back(std::nullopt); - } - } } diff --git a/src/memory/pattern.hpp b/src/memory/pattern.hpp index 23b7e96..a816580 100644 --- a/src/memory/pattern.hpp +++ b/src/memory/pattern.hpp @@ -11,18 +11,17 @@ namespace memory { class pattern { - friend pattern_batch; + friend batch; friend range; public: pattern(std::string_view ida_sig); - explicit pattern(const void* bytes, std::string_view mask); inline pattern(const char* ida_sig) : pattern(std::string_view(ida_sig)) { } - std::vector> m_bytes; + std::vector> m_bytes; }; } diff --git a/src/memory/pattern_batch.cpp b/src/memory/pattern_batch.cpp deleted file mode 100644 index c92a0ba..0000000 --- a/src/memory/pattern_batch.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "pattern_batch.hpp" - -#include "common.hpp" -#include "range.hpp" -#include "util/is_enhanced.hpp" - -namespace memory -{ - void pattern_batch::add(std::string name, pattern pattern, std::function callback) - { - m_entries.emplace_back(std::move(name), std::move(pattern), std::move(callback)); - } - void pattern_batch::add(std::string name, pattern pattern, int min_version, int max_version, eGameBranch game_branch, std::function callback) - { - m_entries.emplace_back(std::move(name), std::move(pattern), min_version, max_version, game_branch, std::move(callback)); - } - - void pattern_batch::run(range region) - { - bool all_found = true; - for (auto& entry : m_entries) - { - if (entry.m_game_branch != eGameBranch::DontCare && entry.m_game_branch != big::get_game_branch()) - { - continue; - } - if (entry.m_min_version != -1 && entry.m_min_version > big::g_game_version) // g_game_version is not implemented - { - continue; - } - if (entry.m_max_version != -1 && entry.m_max_version < big::g_game_version) - { - continue; - } - - if (auto result = region.scan(entry.m_pattern)) - { - if (entry.m_callback) - { - std::invoke(std::move(entry.m_callback), result); - - big::LOGIF(INFO, - big::g.enable_debug_logs, - "Found '{}' GTA5.exe+0x{:X}", - entry.m_name, - result.as() - region.begin().as()); - } - else - { - all_found = false; - LOG(WARNING) << "Failed to find '" << entry.m_name << "'."; - } - } - else - { - all_found = false; - LOG(WARNING) << "Failed to find '" << entry.m_name << "'."; - } - } - - m_entries.clear(); - if (!all_found) - { - throw std::runtime_error("Failed to find some patterns."); - } - } -} diff --git a/src/memory/range.cpp b/src/memory/range.cpp index ef99244..354af0e 100644 --- a/src/memory/range.cpp +++ b/src/memory/range.cpp @@ -1,3 +1,13 @@ +/** + * @file range.cpp + * + * @copyright GNU General Public License Version 2. + * This file is part of YimMenu. + * YimMenu is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. + * YimMenu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with YimMenu. If not, see . + */ + #include "range.hpp" #include "common.hpp" @@ -11,66 +21,119 @@ namespace memory { } - handle range::begin() + handle range::begin() const { return m_base; } - handle range::end() + handle range::end() const { return m_base.add(m_size); } - std::size_t range::size() + std::size_t range::size() const { return m_size; } - bool range::contains(handle h) + bool range::contains(handle h) const { return h.as() >= begin().as() && h.as() <= end().as(); } - static bool pattern_matches(std::uint8_t* target, const std::optional* sig, std::size_t length) + // https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm + // https://www.youtube.com/watch?v=AuZUeshhy-s + std::optional scan_pattern(const std::optional* sig, std::size_t length, handle begin, std::size_t module_size) { - for (std::size_t i = 0; i < length; ++i) + std::size_t maxShift = length; + std::size_t max_idx = length - 1; + + //Get wildcard index, and store max shiftable byte count + std::size_t wild_card_idx{static_cast(-1)}; + for (int i{static_cast(max_idx - 1)}; i >= 0; --i) { - if (sig[i] && *sig[i] != target[i]) - return false; + if (!sig[i]) + { + maxShift = max_idx - i; + wild_card_idx = i; + break; + } } - return true; - }; + //Store max shiftable bytes for non wildcards. + std::size_t shift_table[UINT8_MAX + 1]{}; + for (std::size_t i{}; i <= UINT8_MAX; ++i) + { + shift_table[i] = maxShift; + } + + //Fill shift table with sig bytes + for (std::size_t i{wild_card_idx + 1}; i != max_idx; ++i) + { + shift_table[*sig[i]] = max_idx - i; + } + + //Loop data + const auto scan_end = module_size - length; + for (std::size_t current_idx{}; current_idx <= scan_end;) + { + for (std::ptrdiff_t sig_idx{(std::ptrdiff_t)max_idx}; sig_idx >= 0; --sig_idx) + { + if (sig[sig_idx] && *begin.add(current_idx + sig_idx).as() != *sig[sig_idx]) + { + current_idx += shift_table[*begin.add(current_idx + max_idx).as()]; + break; + } + else if (sig_idx == 0) + { + return begin.add(current_idx); + } + } + } + return std::nullopt; + } - handle range::scan(pattern const& sig) + std::optional range::scan(pattern const& sig) const { auto data = sig.m_bytes.data(); auto length = sig.m_bytes.size(); - for (std::uintptr_t i = 0; i < m_size - length; ++i) + + if (auto result = scan_pattern(data, length, m_base, m_size); result) { - if (pattern_matches(m_base.add(i).as(), data, length)) + return result; + } + + return std::nullopt; + } + + bool pattern_matches(uint8_t* target, const std::optional* sig, std::size_t length) + { + for (std::size_t i{}; i != length; ++i) + { + if (sig[i] && *sig[i] != target[i]) { - return m_base.add(i); + return false; } } - return nullptr; + return true; } - std::vector range::scan_all(pattern const& sig) + std::vector range::scan_all(pattern const& sig) const { - std::vector result; - + std::vector result{}; auto data = sig.m_bytes.data(); auto length = sig.m_bytes.size(); - for (std::uintptr_t i = 0; i < m_size - length; ++i) + + const auto scan_end = m_size - length; + for (std::uintptr_t i{}; i != scan_end; ++i) { - if (pattern_matches(m_base.add(i).as(), data, length)) + if (pattern_matches(m_base.add(i).as(), data, length)) { result.push_back(m_base.add(i)); } } - return std::move(result); + return result; } } diff --git a/src/memory/range.hpp b/src/memory/range.hpp index 02fb7b2..a2438e8 100644 --- a/src/memory/range.hpp +++ b/src/memory/range.hpp @@ -11,14 +11,14 @@ namespace memory public: range(handle base, std::size_t size); - handle begin(); - handle end(); - std::size_t size(); + handle begin() const; + handle end() const; + std::size_t size() const; - bool contains(handle h); + bool contains(handle h) const; - handle scan(pattern const& sig); - std::vector scan_all(pattern const& sig); + std::optional scan(pattern const& sig) const; + std::vector scan_all(pattern const& sig) const; protected: handle m_base; diff --git a/src/pointers.cpp b/src/pointers.cpp index a781395..dc6abef 100644 --- a/src/pointers.cpp +++ b/src/pointers.cpp @@ -13,7 +13,7 @@ namespace big { pointers::pointers() { - memory::pattern_batch main_batch; + memory::batch main_batch; main_batch.add("Ped factory", "48 8B 05 ? ? ? ? 48 8B 48 08 48 85 C9 74 52 8B 81", -1, -1, eGameBranch::Legacy, [this](memory::handle ptr) { m_ped_factory = ptr.add(3).rip().as();