1010#include < memory>
1111#include < optional>
1212#include < span>
13+ #include < string>
1314#include < string_view>
1415#include < system_error>
16+ #include < type_traits>
1517#include < vector>
1618
19+
1720namespace blook {
1821class Process ;
1922
@@ -38,6 +41,7 @@ class Pointer {
3841
3942public:
4043 std::shared_ptr<Process> proc = nullptr ;
44+ bool is_self () const ;
4145 static void *malloc_rwx (size_t size);
4246
4347 static void protect_rwx (void *p, size_t size);
@@ -64,47 +68,120 @@ class Pointer {
6468 Pointer malloc (size_t size,
6569 MemoryProtection protection = MemoryProtection::rw);
6670
67- std::vector< uint8_t > read ( void *ptr, size_t size) const ;
71+ std::optional<Thread> create_thread ( bool suspended = false ) ;
6872
69- std::span<uint8_t > read_leaked (void *ptr, size_t size);
73+ // Read operations
74+ /* *
75+ * @brief Try to read a byte array from the pointer.
76+ * This function will check if the memory is readable before reading in try
77+ * mode.
78+ */
79+ std::expected<std::vector<uint8_t >, std::string>
80+ try_read_bytearray (size_t size) const ;
81+
82+ std::expected<Pointer, std::string> try_read_pointer () const ;
83+
84+ template <typename T>
85+ requires std::is_standard_layout_v<T> && std::is_trivial_v<T>
86+ std::expected<T, std::string> try_read_struct () const {
87+ T val;
88+ auto res = try_read_into (&val, sizeof (T));
89+ if (!res)
90+ return std::unexpected (res.error ());
91+ return val;
92+ }
7093
71- std::expected<void , std::string> write (void *addr, std::span<uint8_t >) const ;
94+ std::expected<std::string, std::string>
95+ try_read_utf8_string (size_t length = -1 ) const ;
7296
73- std::optional<Thread> create_thread (bool suspended = false );
97+ std::expected<std::string, std::string>
98+ try_read_utf16_string (size_t length = -1 ) const ;
7499
75- template <typename Struct>
76- inline std::optional<Struct *> read_leaked (void *ptr = nullptr ) {
77- const auto val = read_leaked (ptr, sizeof (Struct));
78- return reinterpret_cast <Struct *>(val.data ());
79- }
100+ // Write operations
101+ std::expected<void , std::string>
102+ try_write_bytearray (std::span<const uint8_t > data) const ;
80103
81- template <typename Struct>
82- [[nodiscard]] inline Struct read (size_t ptr = 0 ) const {
83- auto data = try_read ((void *)ptr, sizeof (Struct));
84- if (!data.has_value ()) {
85- throw std::runtime_error (" Failed to read memory" );
86- }
87- return *reinterpret_cast <Struct *>(data.value ().data ());
88- }
104+ std::expected<void , std::string> try_write_pointer (Pointer ptr) const ;
89105
90- template <typename Struct>
91- [[nodiscard]] inline auto write (Struct data, size_t ptr = 0 ) {
92- return write ((void *)ptr, std::span ((uint8_t *)&data, sizeof (Struct)));
106+ template <typename T>
107+ requires std::is_standard_layout_v<T> && std::is_trivial_v<T>
108+ std::expected<void , std::string> try_write_struct (const T &value) const {
109+ return try_write_bytearray (
110+ std::span<const uint8_t >((const uint8_t *)&value, sizeof (T)));
93111 }
94112
95- template <typename Struct>
96- [[nodiscard]] inline std::optional<Struct> try_read (size_t ptr = 0 ) const {
97- auto data = try_read ((void *)ptr, sizeof (Struct));
98- if (!data.has_value ()) {
99- return {};
100- }
101- return *reinterpret_cast <Struct *>(data.value ().data ());
113+ // Raw read into buffer, avoids extra vector allocation
114+ std::expected<void , std::string> try_read_into (void *dest, size_t size) const ;
115+ void read_into (void *dest, size_t size) const ;
116+
117+ // Throwing wrappers
118+ /* *
119+ * @brief Read a byte array from the pointer.
120+ * Note: This function does NOT perform extra safety checks like
121+ * try_read_bytearray. It is intended for high-performance use when memory is
122+ * known to be valid.
123+ */
124+ std::vector<uint8_t > read_bytearray (size_t size) const ;
125+ Pointer read_pointer () const ;
126+
127+ template <typename T>
128+ requires std::is_standard_layout_v<T> && std::is_trivial_v<T>
129+ T read_struct () const {
130+ T val;
131+ read_into (&val, sizeof (T));
132+ return val;
102133 }
103134
104- std::optional<std::vector<uint8_t >> try_read (void *ptr, size_t size) const ;
135+ std::string read_utf8_string (size_t length = -1 ) const ;
136+ std::string read_utf16_string (size_t length = -1 ) const ;
137+
138+ void write_bytearray (std::span<const uint8_t > data) const ;
139+ void write_pointer (Pointer ptr) const ;
140+
141+ template <typename T> void write_struct (const T &value) const {
142+ auto res = try_write_struct<T>(value);
143+ if (!res)
144+ throw std::runtime_error (res.error ());
145+ }
105146
106- // read to
107- void *read (std::span<uint8_t > dest) const ;
147+ #define BLOOK_READ_WRITE_HELPER (type, name ) \
148+ inline std::expected<type, std::string> try_read_##name() const { \
149+ type val; \
150+ auto res = try_read_into (&val, sizeof (type)); \
151+ if (!res) \
152+ return std::unexpected (res.error ()); \
153+ return val; \
154+ } \
155+ inline type read_##name() const { \
156+ type val; \
157+ read_into (&val, sizeof (type)); \
158+ return val; \
159+ } \
160+ inline std::expected<void , std::string> try_write_##name(type val) const { \
161+ return try_write_struct<type>(val); \
162+ } \
163+ inline void write_##name(type val) const { write_struct<type>(val); }
164+
165+ BLOOK_READ_WRITE_HELPER (int8_t , s8)
166+ BLOOK_READ_WRITE_HELPER (int16_t , s16)
167+ BLOOK_READ_WRITE_HELPER (int32_t , s32)
168+ BLOOK_READ_WRITE_HELPER (int64_t , s64)
169+ BLOOK_READ_WRITE_HELPER (uint8_t , u8 )
170+ BLOOK_READ_WRITE_HELPER (uint16_t , u16 )
171+ BLOOK_READ_WRITE_HELPER (uint32_t , u32 )
172+ BLOOK_READ_WRITE_HELPER (uint64_t , u64 )
173+ BLOOK_READ_WRITE_HELPER (short , short )
174+ BLOOK_READ_WRITE_HELPER (unsigned short , ushort)
175+ BLOOK_READ_WRITE_HELPER (int , int )
176+ BLOOK_READ_WRITE_HELPER (unsigned int , uint)
177+ BLOOK_READ_WRITE_HELPER (float , float )
178+ BLOOK_READ_WRITE_HELPER (double , double )
179+ #undef BLOOK_READ_WRITE_HELPER
180+
181+ template <typename T> T read_volatile () const {
182+ // For now, same as read_struct.
183+ return read_struct<T>();
184+ }
108185
109186 explicit Pointer (std::shared_ptr<Process> proc);
110187
@@ -175,6 +252,11 @@ struct ScopedSetMemoryRWX {
175252 void *old_protect;
176253
177254 ScopedSetMemoryRWX (void *ptr, size_t size);
255+ ScopedSetMemoryRWX (const Pointer &p, size_t size)
256+ : ScopedSetMemoryRWX(p.data(), size) {}
257+ ScopedSetMemoryRWX (const ScopedSetMemoryRWX &) = delete ;
258+ ScopedSetMemoryRWX &operator =(const ScopedSetMemoryRWX &) = delete ;
259+ ScopedSetMemoryRWX (const MemoryRange &r);
178260
179261 ~ScopedSetMemoryRWX ();
180262};
@@ -229,25 +311,40 @@ class MemoryRange : public Pointer {
229311
230312 MemoryIteratorBuffered &operator =(MemoryIteratorBuffered &&) = default ;
231313
232- MemoryIteratorBuffered (Pointer ptr, size_t size) : ptr(ptr), size(size) {}
314+ MemoryIteratorBuffered (Pointer ptr, size_t size) : ptr(ptr), size(size) {
315+ this ->cache = std::make_shared<CacheBuffer>();
316+ }
233317 MemoryIteratorBuffered (Pointer ptr, size_t size,
234318 std::shared_ptr<CacheBuffer> cache)
235- : ptr(ptr), size(size), cache(cache) {}
319+ : ptr(ptr), size(size), cache(cache) {
320+ if (!this ->cache )
321+ this ->cache = std::make_shared<CacheBuffer>();
322+ }
236323
237324 inline MemoryIteratorBuffered &operator +=(size_t t) {
238- if (t * step > size) {
239- this ->ptr = ptr + size;
325+ size_t sub = t * step;
326+ if (sub > size) {
327+ ptr += size;
240328 size = 0 ;
241329 } else {
242- ptr += t * step ;
243- size -= t * step ;
330+ ptr += sub ;
331+ size -= sub ;
244332 }
245-
246333 return *this ;
247334 }
248335
249- inline MemoryIteratorBuffered operator +(size_t t) {
250- return {ptr + t * step, std::max (size - t * step, (size_t )0 ), cache};
336+ inline MemoryIteratorBuffered operator +(size_t t) const {
337+ size_t sub = t * step;
338+ size_t new_size = size;
339+ Pointer new_ptr = ptr;
340+ if (sub > size) {
341+ new_ptr += size;
342+ new_size = 0 ;
343+ } else {
344+ new_ptr += sub;
345+ new_size -= sub;
346+ }
347+ return {new_ptr, new_size, cache};
251348 }
252349
253350 inline MemoryIteratorBuffered &operator ++() {
@@ -273,20 +370,36 @@ class MemoryRange : public Pointer {
273370 inline bool is_readable () const { return try_read ().has_value (); }
274371
275372 inline std::optional<uint8_t > try_read () const {
373+ if (size == 0 )
374+ return {};
375+
276376 if (cache->buffer
277377 .empty () || /* !(cache->offset ∈ [ptr, ptr+cache->size]) */
278- cache->offset > ptr.offset () ||
279- cache->offset + cache->buffer .size () <= ptr.offset ()) {
280- cache->buffer .resize (std::min (bufSize, size));
281- if (!ptr.read (std::span (cache->buffer .data (), cache->buffer .size ())))
378+ ptr.offset () < cache->offset ||
379+ ptr.offset () >= cache->offset + cache->buffer .size ()) {
380+ auto read_size = std::min (bufSize, size);
381+ cache->buffer .resize (read_size);
382+ auto res = ptr.try_read_into (cache->buffer .data (), read_size);
383+ if (!res) {
384+ cache->buffer .clear ();
282385 return {};
386+ }
283387 cache->offset = ptr.offset ();
284388 }
285389
286- return cache->buffer [ptr.offset () - cache->offset ];
390+ const size_t internal_offset = ptr.offset () - cache->offset ;
391+ if (internal_offset >= cache->buffer .size ())
392+ return {};
393+
394+ return cache->buffer [internal_offset];
287395 }
288396
289- inline uint8_t operator *() { return try_read ().value (); }
397+ inline uint8_t operator *() {
398+ auto val = try_read ();
399+ if (!val)
400+ throw std::runtime_error (" Failed to read memory through iterator" );
401+ return *val;
402+ }
290403
291404 using value_type = uint8_t ;
292405 using difference_type = std::ptrdiff_t ;
@@ -313,7 +426,8 @@ class MemoryRange : public Pointer {
313426 template <class Scanner = memory_scanner::mb_kmp>
314427 inline std::optional<Pointer>
315428 find_one (const std::vector<uint8_t > pattern) const {
316- std::optional<size_t > res = Scanner::searchOne ((uint8_t *)_offset, _size, pattern);
429+ std::optional<size_t > res =
430+ Scanner::searchOne ((uint8_t *)_offset, _size, pattern);
317431 return res.and_then ([this ](const auto val) {
318432 return std::optional<Pointer>(Pointer (this ->proc , this ->_offset + val));
319433 });
0 commit comments