robloxmemoryapi 0.0.2__tar.gz → 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {robloxmemoryapi-0.0.2/src/robloxmemoryapi.egg-info → robloxmemoryapi-0.0.4}/PKG-INFO +16 -2
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/README.md +14 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/pyproject.toml +2 -2
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi/__init__.py +13 -2
- robloxmemoryapi-0.0.4/src/robloxmemoryapi/data/offsets.json +6 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi/utils/memory.py +61 -2
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi/utils/offsets.py +2 -2
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi/utils/rbx/instance.py +289 -21
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4/src/robloxmemoryapi.egg-info}/PKG-INFO +16 -2
- robloxmemoryapi-0.0.2/src/robloxmemoryapi/data/offsets.json +0 -6
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/LICENSE.md +0 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/setup.cfg +0 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi/utils/__init__.py +0 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi/utils/rbx/__init__.py +0 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi/utils/rbx/datastructures.py +0 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi.egg-info/SOURCES.txt +0 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi.egg-info/dependency_links.txt +0 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi.egg-info/requires.txt +0 -0
- {robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robloxmemoryapi
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary: Python Library that abstracts reading data from the Roblox DataModel
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Python Library that abstracts reading and writing data from the Roblox DataModel
|
|
5
5
|
Author-email: upio <notpoiu@users.noreply.github.com>, mstudio45 <mstudio45@users.noreply.github.com>, ActualMasterOogway <ActualMasterOogway@users.noreply.github.com>
|
|
6
6
|
License: Copyright 2025 upio, mstudio45, master oogway
|
|
7
7
|
|
|
@@ -58,12 +58,26 @@ from robloxmemoryapi import RobloxGameClient
|
|
|
58
58
|
client = RobloxGameClient()
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
To request write access (e.g. to patch values), pass `allow_write=True` when creating the client.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
client = RobloxGameClient(allow_write=True)
|
|
65
|
+
```
|
|
66
|
+
|
|
61
67
|
Access the data model:
|
|
62
68
|
|
|
63
69
|
```python
|
|
64
70
|
game = client.DataModel
|
|
65
71
|
```
|
|
66
72
|
|
|
73
|
+
Kill the local player:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# (requires allow_write=True when creating the client)
|
|
77
|
+
# allow_write may be detected by roblox. It is disabled by default.
|
|
78
|
+
game.Players.LocalPlayer.Character.Humanoid.Health = 0
|
|
79
|
+
```
|
|
80
|
+
|
|
67
81
|
Get the local player's name:
|
|
68
82
|
|
|
69
83
|
```python
|
|
@@ -30,12 +30,26 @@ from robloxmemoryapi import RobloxGameClient
|
|
|
30
30
|
client = RobloxGameClient()
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
To request write access (e.g. to patch values), pass `allow_write=True` when creating the client.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
client = RobloxGameClient(allow_write=True)
|
|
37
|
+
```
|
|
38
|
+
|
|
33
39
|
Access the data model:
|
|
34
40
|
|
|
35
41
|
```python
|
|
36
42
|
game = client.DataModel
|
|
37
43
|
```
|
|
38
44
|
|
|
45
|
+
Kill the local player:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
# (requires allow_write=True when creating the client)
|
|
49
|
+
# allow_write may be detected by roblox. It is disabled by default.
|
|
50
|
+
game.Players.LocalPlayer.Character.Humanoid.Health = 0
|
|
51
|
+
```
|
|
52
|
+
|
|
39
53
|
Get the local player's name:
|
|
40
54
|
|
|
41
55
|
```python
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "robloxmemoryapi"
|
|
7
|
-
version = "0.0.
|
|
8
|
-
description = "Python Library that abstracts reading data from the Roblox DataModel"
|
|
7
|
+
version = "0.0.4"
|
|
8
|
+
description = "Python Library that abstracts reading and writing data from the Roblox DataModel"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = { file = "LICENSE.md" }
|
|
@@ -47,7 +47,12 @@ class RobloxRandom:
|
|
|
47
47
|
return ((u * r) >> 32) + lo
|
|
48
48
|
|
|
49
49
|
class RobloxGameClient:
|
|
50
|
-
def __init__(
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
pid: int = None,
|
|
53
|
+
process_name: str = "RobloxPlayerBeta.exe",
|
|
54
|
+
allow_write: bool = False,
|
|
55
|
+
):
|
|
51
56
|
if platform.system() != "Windows":
|
|
52
57
|
self.failed = True
|
|
53
58
|
return
|
|
@@ -56,6 +61,8 @@ class RobloxGameClient:
|
|
|
56
61
|
EvasiveProcess,
|
|
57
62
|
PROCESS_QUERY_INFORMATION,
|
|
58
63
|
PROCESS_VM_READ,
|
|
64
|
+
PROCESS_VM_WRITE,
|
|
65
|
+
PROCESS_VM_OPERATION,
|
|
59
66
|
get_pid_by_name,
|
|
60
67
|
)
|
|
61
68
|
|
|
@@ -67,7 +74,11 @@ class RobloxGameClient:
|
|
|
67
74
|
if self.pid is None or self.pid == 0:
|
|
68
75
|
raise ValueError("Failed to get PID.")
|
|
69
76
|
|
|
70
|
-
|
|
77
|
+
desired_access = PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
|
|
78
|
+
if allow_write:
|
|
79
|
+
desired_access |= PROCESS_VM_WRITE | PROCESS_VM_OPERATION
|
|
80
|
+
|
|
81
|
+
self.memory_module = EvasiveProcess(self.pid, desired_access)
|
|
71
82
|
self.failed = False
|
|
72
83
|
|
|
73
84
|
def close(self):
|
|
@@ -20,6 +20,8 @@ BOOL = wintypes.BOOL
|
|
|
20
20
|
|
|
21
21
|
PROCESS_QUERY_INFORMATION = 0x0400
|
|
22
22
|
PROCESS_VM_READ = 0x0010
|
|
23
|
+
PROCESS_VM_WRITE = 0x0020
|
|
24
|
+
PROCESS_VM_OPERATION = 0x0008
|
|
23
25
|
LIST_MODULES_ALL = 0x03
|
|
24
26
|
STATUS_SUCCESS = 0
|
|
25
27
|
MEM_COMMIT = 0x1000
|
|
@@ -79,17 +81,27 @@ NtReadVirtualMemoryProto = ctypes.WINFUNCTYPE(
|
|
|
79
81
|
ctypes.c_ulong,
|
|
80
82
|
ctypes.POINTER(ctypes.c_ulong)
|
|
81
83
|
)
|
|
84
|
+
NtWriteVirtualMemoryProto = ctypes.WINFUNCTYPE(
|
|
85
|
+
NTSTATUS,
|
|
86
|
+
HANDLE,
|
|
87
|
+
LPVOID,
|
|
88
|
+
LPVOID,
|
|
89
|
+
ctypes.c_ulong,
|
|
90
|
+
ctypes.POINTER(ctypes.c_ulong)
|
|
91
|
+
)
|
|
82
92
|
NtCloseProto = ctypes.WINFUNCTYPE(NTSTATUS, HANDLE)
|
|
83
93
|
|
|
84
94
|
syscall_id_open = get_syscall_number("NtOpenProcess")
|
|
85
95
|
syscall_id_read = get_syscall_number("NtReadVirtualMemory")
|
|
96
|
+
syscall_id_write = get_syscall_number("NtWriteVirtualMemory")
|
|
86
97
|
syscall_id_close = get_syscall_number("NtClose")
|
|
87
98
|
|
|
88
|
-
if not all([syscall_id_open, syscall_id_read, syscall_id_close]):
|
|
99
|
+
if not all([syscall_id_open, syscall_id_read, syscall_id_write, syscall_id_close]):
|
|
89
100
|
raise RuntimeError("Could not find required syscall numbers.")
|
|
90
101
|
|
|
91
102
|
nt_open_process_syscall = create_syscall_function(syscall_id_open, NtOpenProcessProto)
|
|
92
103
|
nt_read_virtual_memory_syscall = create_syscall_function(syscall_id_read, NtReadVirtualMemoryProto)
|
|
104
|
+
nt_write_virtual_memory_syscall = create_syscall_function(syscall_id_write, NtWriteVirtualMemoryProto)
|
|
93
105
|
nt_close_syscall = create_syscall_function(syscall_id_close, NtCloseProto)
|
|
94
106
|
|
|
95
107
|
psapi.EnumProcessModulesEx.argtypes = [
|
|
@@ -204,6 +216,27 @@ class EvasiveProcess:
|
|
|
204
216
|
raise OSError(f"NtReadVirtualMemory failed with NTSTATUS: 0x{status:X}")
|
|
205
217
|
return buffer.raw[:bytes_read.value]
|
|
206
218
|
|
|
219
|
+
def write(self, address: int, data: bytes | bytearray) -> int:
|
|
220
|
+
if not self.handle or self.handle.value == 0:
|
|
221
|
+
raise ValueError("Process handle is not valid.")
|
|
222
|
+
if not isinstance(data, (bytes, bytearray)):
|
|
223
|
+
raise TypeError("data must be bytes-like.")
|
|
224
|
+
raw = bytes(data)
|
|
225
|
+
if len(raw) == 0:
|
|
226
|
+
return 0
|
|
227
|
+
buffer = (ctypes.c_ubyte * len(raw)).from_buffer_copy(raw)
|
|
228
|
+
bytes_written = ctypes.c_ulong(0)
|
|
229
|
+
status = nt_write_virtual_memory_syscall(
|
|
230
|
+
self.handle,
|
|
231
|
+
LPVOID(address),
|
|
232
|
+
buffer,
|
|
233
|
+
len(raw),
|
|
234
|
+
ctypes.byref(bytes_written)
|
|
235
|
+
)
|
|
236
|
+
if status != STATUS_SUCCESS:
|
|
237
|
+
raise OSError(f"NtWriteVirtualMemory failed with NTSTATUS: 0x{status:X}")
|
|
238
|
+
return bytes_written.value
|
|
239
|
+
|
|
207
240
|
# numbers #
|
|
208
241
|
def read_int(self, address: int) -> int:
|
|
209
242
|
buffer = self.read(address, 4)
|
|
@@ -243,6 +276,23 @@ class EvasiveProcess:
|
|
|
243
276
|
return floats
|
|
244
277
|
except (OSError, struct.error) as e:
|
|
245
278
|
return [0.0]
|
|
279
|
+
|
|
280
|
+
def write_int(self, address: int, value: int) -> None:
|
|
281
|
+
self.write(address, struct.pack('<I', value & 0xFFFFFFFF))
|
|
282
|
+
|
|
283
|
+
def write_long(self, address: int, value: int) -> None:
|
|
284
|
+
self.write(address, struct.pack('<Q', value & 0xFFFFFFFFFFFFFFFF))
|
|
285
|
+
|
|
286
|
+
def write_double(self, address: int, value: float) -> None:
|
|
287
|
+
self.write(address, struct.pack('<d', value))
|
|
288
|
+
|
|
289
|
+
def write_float(self, address: int, value: float) -> None:
|
|
290
|
+
self.write(address, struct.pack('<f', value))
|
|
291
|
+
|
|
292
|
+
def write_floats(self, address: int, values) -> None:
|
|
293
|
+
packed = b''.join(struct.pack('<f', float(v)) for v in values)
|
|
294
|
+
if packed:
|
|
295
|
+
self.write(address, packed)
|
|
246
296
|
|
|
247
297
|
# bool #
|
|
248
298
|
def read_bool(self, address: int) -> bool:
|
|
@@ -252,6 +302,9 @@ class EvasiveProcess:
|
|
|
252
302
|
return bool(int.from_bytes(bool_byte, 'little'))
|
|
253
303
|
except OSError:
|
|
254
304
|
return False
|
|
305
|
+
|
|
306
|
+
def write_bool(self, address: int, value: bool) -> None:
|
|
307
|
+
self.write(address, (1 if value else 0).to_bytes(1, 'little'))
|
|
255
308
|
|
|
256
309
|
# string #
|
|
257
310
|
def read_raw_string(self, address: int, max_length: int = 256) -> str:
|
|
@@ -259,6 +312,12 @@ class EvasiveProcess:
|
|
|
259
312
|
null_pos = buffer.find(b'\x00')
|
|
260
313
|
valid_bytes = buffer[:null_pos] if null_pos != -1 else buffer
|
|
261
314
|
return valid_bytes.decode('utf-8', errors='ignore')
|
|
315
|
+
|
|
316
|
+
def write_raw_string(self, address: int, value: str, null_terminate: bool = True) -> None:
|
|
317
|
+
data = value.encode('utf-8')
|
|
318
|
+
if null_terminate:
|
|
319
|
+
data += b'\x00'
|
|
320
|
+
self.write(address, data)
|
|
262
321
|
|
|
263
322
|
def read_string(self, address: int) -> str:
|
|
264
323
|
string_length = self.read_int(address + 0x10)
|
|
@@ -272,4 +331,4 @@ class EvasiveProcess:
|
|
|
272
331
|
def close(self):
|
|
273
332
|
if self.handle and self.handle.value != 0:
|
|
274
333
|
nt_close_syscall(self.handle)
|
|
275
|
-
self.handle = HANDLE(0)
|
|
334
|
+
self.handle = HANDLE(0)
|
|
@@ -15,17 +15,105 @@ class RBXInstance:
|
|
|
15
15
|
return self.FindFirstChild(key)
|
|
16
16
|
|
|
17
17
|
# utilities #
|
|
18
|
+
def _ensure_writable(self):
|
|
19
|
+
if not hasattr(self.memory_module, "write"):
|
|
20
|
+
raise RuntimeError("Write operations require a memory module with write support (allow_write=True).")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def _as_vector3(value, context="value"):
|
|
24
|
+
if isinstance(value, Vector3):
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
if isinstance(value, (tuple, list)) and len(value) == 3:
|
|
28
|
+
return Vector3(*value)
|
|
29
|
+
|
|
30
|
+
raise TypeError(f"{context} must be a Vector3 or an iterable of three numbers.")
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _as_vector2(value, context="value"):
|
|
34
|
+
if isinstance(value, Vector2):
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
if isinstance(value, (tuple, list)) and len(value) == 2:
|
|
38
|
+
return Vector2(*value)
|
|
39
|
+
|
|
40
|
+
raise TypeError(f"{context} must be a Vector2 or an iterable of two numbers.")
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _as_udim2(value, context="value"):
|
|
44
|
+
if isinstance(value, UDim2):
|
|
45
|
+
return value
|
|
46
|
+
if isinstance(value, (tuple, list)):
|
|
47
|
+
if len(value) == 4:
|
|
48
|
+
return UDim2(value[0], value[1], value[2], value[3])
|
|
49
|
+
if len(value) == 2:
|
|
50
|
+
x, y = value
|
|
51
|
+
if isinstance(x, UDim) and isinstance(y, UDim):
|
|
52
|
+
return UDim2(x.Scale, x.Offset, y.Scale, y.Offset)
|
|
53
|
+
if isinstance(x, (tuple, list)) and isinstance(y, (tuple, list)) and len(x) == 2 and len(y) == 2:
|
|
54
|
+
return UDim2(x[0], x[1], y[0], y[1])
|
|
55
|
+
raise TypeError(f"{context} must be a UDim2 or a compatible iterable.")
|
|
56
|
+
|
|
57
|
+
# memory helpers #
|
|
58
|
+
def _write_rbx_string(self, address: int, value: str):
|
|
59
|
+
self._ensure_writable()
|
|
60
|
+
|
|
61
|
+
if not isinstance(value, str):
|
|
62
|
+
raise TypeError("value must be a string.")
|
|
63
|
+
|
|
64
|
+
if address == 0:
|
|
65
|
+
raise ValueError("String address is null; cannot write.")
|
|
66
|
+
|
|
67
|
+
encoded = value.encode('utf-8')
|
|
68
|
+
if len(encoded) > 15:
|
|
69
|
+
raise ValueError("String too long (max 15 UTF-8 bytes supported for inline Roblox strings).")
|
|
70
|
+
|
|
71
|
+
current_length = self.memory_module.read_int(address + 0x10)
|
|
72
|
+
if current_length > 15:
|
|
73
|
+
raise ValueError("Existing string is heap allocated; inline overwrite is not supported.")
|
|
74
|
+
|
|
75
|
+
padded = encoded + b'\x00'
|
|
76
|
+
if len(padded) < 16:
|
|
77
|
+
padded += b'\x00' * (16 - len(padded))
|
|
78
|
+
|
|
79
|
+
self.memory_module.write(address, padded[:16])
|
|
80
|
+
self.memory_module.write_int(address + 0x10, len(encoded))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _read_udim2(self, address: int) -> UDim2:
|
|
84
|
+
if not isinstance(address, int):
|
|
85
|
+
raise TypeError("address must be an int.")
|
|
86
|
+
|
|
87
|
+
scale_x = self.memory_module.read_float(address)
|
|
88
|
+
offset_x = self.memory_module.read_int(address + 0x4)
|
|
89
|
+
scale_y = self.memory_module.read_float(address + 0x8)
|
|
90
|
+
offset_y = self.memory_module.read_int(address + 0xC)
|
|
91
|
+
|
|
92
|
+
return UDim2(scale_x, offset_x, scale_y, offset_y)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _write_udim2(self, address: int, value: UDim2):
|
|
96
|
+
value = self._as_udim2(value, "UDim2")
|
|
97
|
+
|
|
98
|
+
if not isinstance(value, UDim2):
|
|
99
|
+
raise TypeError("value must be a UDim2.")
|
|
100
|
+
|
|
101
|
+
self._ensure_writable()
|
|
102
|
+
|
|
103
|
+
self.memory_module.write_float(address, value.X.Scale)
|
|
104
|
+
self.memory_module.write_int(address + 0x4, value.X.Offset)
|
|
105
|
+
|
|
106
|
+
self.memory_module.write_float(address + 0x8, value.Y.Scale)
|
|
107
|
+
self.memory_module.write_int(address + 0xC, value.Y.Offset)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# useful pointer stuff #
|
|
18
111
|
@property
|
|
19
112
|
def primitive_address(self):
|
|
20
113
|
part_primitive_pointer = self.raw_address + Offsets["Primitive"]
|
|
21
114
|
part_primitive = int.from_bytes(self.memory_module.read(part_primitive_pointer, 8), 'little')
|
|
22
115
|
return part_primitive
|
|
23
116
|
|
|
24
|
-
@property
|
|
25
|
-
def on_demand_instance_address(self):
|
|
26
|
-
part_primitive_pointer = self.raw_address + Offsets["OnDemandInstance"]
|
|
27
|
-
part_primitive = int.from_bytes(self.memory_module.read(part_primitive_pointer, 8), 'little')
|
|
28
|
-
return part_primitive
|
|
29
117
|
|
|
30
118
|
# props #
|
|
31
119
|
@property
|
|
@@ -36,12 +124,32 @@ class RBXInstance:
|
|
|
36
124
|
|
|
37
125
|
return RBXInstance(parent_pointer, self.memory_module)
|
|
38
126
|
|
|
127
|
+
@Parent.setter
|
|
128
|
+
def Parent(self, value):
|
|
129
|
+
if value is None:
|
|
130
|
+
target = 0
|
|
131
|
+
elif isinstance(value, RBXInstance):
|
|
132
|
+
target = value.raw_address
|
|
133
|
+
elif isinstance(value, int):
|
|
134
|
+
target = value
|
|
135
|
+
else:
|
|
136
|
+
raise TypeError("Parent must be set to an RBXInstance, int address, or None.")
|
|
137
|
+
self._ensure_writable()
|
|
138
|
+
self.memory_module.write_long(self.raw_address + Offsets["Parent"], target)
|
|
139
|
+
|
|
39
140
|
@property
|
|
40
141
|
def Name(self):
|
|
41
142
|
name_address_pointer = self.raw_address + Offsets["Name"]
|
|
42
143
|
name_address = int.from_bytes(self.memory_module.read(name_address_pointer, 8), 'little')
|
|
43
144
|
return self.memory_module.read_string(name_address)
|
|
44
145
|
|
|
146
|
+
@Name.setter
|
|
147
|
+
def Name(self, value: str):
|
|
148
|
+
self._ensure_writable()
|
|
149
|
+
name_address_pointer = self.raw_address + Offsets["Name"]
|
|
150
|
+
name_address = int.from_bytes(self.memory_module.read(name_address_pointer, 8), 'little')
|
|
151
|
+
self._write_rbx_string(name_address, value)
|
|
152
|
+
|
|
45
153
|
@property
|
|
46
154
|
def ClassName(self):
|
|
47
155
|
class_descriptor_address = int.from_bytes(
|
|
@@ -79,6 +187,29 @@ class RBXInstance:
|
|
|
79
187
|
Vector3(*LookVectorData)
|
|
80
188
|
)
|
|
81
189
|
|
|
190
|
+
@CFrame.setter
|
|
191
|
+
def CFrame(self, value: "CFrame"):
|
|
192
|
+
if not isinstance(value, CFrame):
|
|
193
|
+
raise TypeError("CFrame setter expects a CFrame value.")
|
|
194
|
+
self._ensure_writable()
|
|
195
|
+
|
|
196
|
+
matrix_data = [
|
|
197
|
+
value.RightVector.X, value.UpVector.X, -value.LookVector.X,
|
|
198
|
+
value.RightVector.Y, value.UpVector.Y, -value.LookVector.Y,
|
|
199
|
+
value.RightVector.Z, value.UpVector.Z, -value.LookVector.Z,
|
|
200
|
+
value.Position.X, value.Position.Y, value.Position.Z
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
className = self.ClassName
|
|
204
|
+
if "part" in className.lower():
|
|
205
|
+
base_address = self.primitive_address + Offsets["CFrame"]
|
|
206
|
+
elif className == "Camera":
|
|
207
|
+
base_address = self.raw_address + Offsets["CameraCFrame"]
|
|
208
|
+
else:
|
|
209
|
+
raise AttributeError("CFrame cannot be written for this instance type.")
|
|
210
|
+
|
|
211
|
+
self.memory_module.write_floats(base_address, matrix_data)
|
|
212
|
+
|
|
82
213
|
@property
|
|
83
214
|
def Position(self):
|
|
84
215
|
className = self.ClassName
|
|
@@ -89,17 +220,30 @@ class RBXInstance:
|
|
|
89
220
|
position_vector3 = self.memory_module.read_floats(self.raw_address + Offsets["CameraPos"], 3)
|
|
90
221
|
return Vector3(*position_vector3)
|
|
91
222
|
else:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
223
|
+
return self._read_udim2(self.raw_address + Offsets["FramePositionX"])
|
|
224
|
+
|
|
225
|
+
@Position.setter
|
|
226
|
+
def Position(self, value):
|
|
227
|
+
className = self.ClassName
|
|
228
|
+
|
|
229
|
+
self._ensure_writable()
|
|
230
|
+
if "part" in className.lower():
|
|
231
|
+
vec = self._as_vector3(value, "Position")
|
|
232
|
+
self.memory_module.write_floats(
|
|
233
|
+
self.primitive_address + Offsets["Position"],
|
|
234
|
+
(vec.X, vec.Y, vec.Z)
|
|
235
|
+
)
|
|
95
236
|
|
|
96
|
-
|
|
97
|
-
|
|
237
|
+
elif className == "Camera":
|
|
238
|
+
vec = self._as_vector3(value, "Position")
|
|
239
|
+
self.memory_module.write_floats(
|
|
240
|
+
self.raw_address + Offsets["CameraPos"],
|
|
241
|
+
(vec.X, vec.Y, vec.Z)
|
|
242
|
+
)
|
|
98
243
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return (0.0, 0, 0.0, 0)
|
|
244
|
+
else:
|
|
245
|
+
udim2_value = self._as_udim2(value, "Position")
|
|
246
|
+
self._write_udim2(self.raw_address + Offsets["FramePositionX"], udim2_value)
|
|
103
247
|
|
|
104
248
|
@property
|
|
105
249
|
def Velocity(self):
|
|
@@ -111,19 +255,40 @@ class RBXInstance:
|
|
|
111
255
|
|
|
112
256
|
return None
|
|
113
257
|
|
|
258
|
+
@Velocity.setter
|
|
259
|
+
def Velocity(self, value):
|
|
260
|
+
className = self.ClassName
|
|
261
|
+
if "part" not in className.lower():
|
|
262
|
+
raise AttributeError("Velocity can only be written for BasePart-derived instances.")
|
|
263
|
+
|
|
264
|
+
vec = self._as_vector3(value, "Velocity")
|
|
265
|
+
|
|
266
|
+
self._ensure_writable()
|
|
267
|
+
self.memory_module.write_floats(
|
|
268
|
+
self.primitive_address + Offsets["Velocity"],
|
|
269
|
+
(vec.X, vec.Y, vec.Z)
|
|
270
|
+
)
|
|
271
|
+
|
|
114
272
|
@property
|
|
115
273
|
def Size(self):
|
|
116
274
|
if "part" in self.ClassName.lower():
|
|
117
275
|
size_vector3 = self.memory_module.read_floats(self.primitive_address + Offsets["PartSize"], 3)
|
|
118
276
|
return Vector3(*size_vector3)
|
|
119
277
|
else:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
278
|
+
return self._read_udim2(self.raw_address + Offsets["FrameSizeX"])
|
|
279
|
+
|
|
280
|
+
@Size.setter
|
|
281
|
+
def Size(self, value):
|
|
282
|
+
self._ensure_writable()
|
|
283
|
+
if "part" in self.ClassName.lower():
|
|
284
|
+
vec = self._as_vector3(value, "Size")
|
|
285
|
+
self.memory_module.write_floats(
|
|
286
|
+
self.primitive_address + Offsets["PartSize"],
|
|
287
|
+
(vec.X, vec.Y, vec.Z)
|
|
288
|
+
)
|
|
289
|
+
else:
|
|
290
|
+
gui_size = self._as_udim2(value, "Size")
|
|
291
|
+
self._write_udim2(self.raw_address + Offsets["FrameSizeX"], gui_size)
|
|
127
292
|
|
|
128
293
|
# XXXXValue props #
|
|
129
294
|
@property
|
|
@@ -148,6 +313,33 @@ class RBXInstance:
|
|
|
148
313
|
return RBXInstance(object_address, self.memory_module)
|
|
149
314
|
|
|
150
315
|
return None
|
|
316
|
+
|
|
317
|
+
@Value.setter
|
|
318
|
+
def Value(self, new_value):
|
|
319
|
+
self._ensure_writable()
|
|
320
|
+
classname = self.ClassName
|
|
321
|
+
value_address = self.raw_address + Offsets["Value"]
|
|
322
|
+
|
|
323
|
+
if classname == "StringValue":
|
|
324
|
+
self._write_rbx_string(value_address, str(new_value))
|
|
325
|
+
elif classname == "IntValue":
|
|
326
|
+
self.memory_module.write_int(value_address, int(new_value))
|
|
327
|
+
elif classname == "NumberValue":
|
|
328
|
+
self.memory_module.write_double(value_address, float(new_value))
|
|
329
|
+
elif classname == "BoolValue":
|
|
330
|
+
self.memory_module.write_bool(value_address, bool(new_value))
|
|
331
|
+
elif classname == "ObjectValue":
|
|
332
|
+
if new_value is None:
|
|
333
|
+
target = 0
|
|
334
|
+
elif isinstance(new_value, RBXInstance):
|
|
335
|
+
target = new_value.raw_address
|
|
336
|
+
elif isinstance(new_value, int):
|
|
337
|
+
target = new_value
|
|
338
|
+
else:
|
|
339
|
+
raise TypeError("ObjectValue.Value must be set to an RBXInstance, int address, or None.")
|
|
340
|
+
self.memory_module.write_long(value_address, target)
|
|
341
|
+
else:
|
|
342
|
+
raise AttributeError(f"Writing Value is not supported for class {classname}.")
|
|
151
343
|
|
|
152
344
|
# text props #
|
|
153
345
|
@property
|
|
@@ -157,6 +349,12 @@ class RBXInstance:
|
|
|
157
349
|
|
|
158
350
|
return None
|
|
159
351
|
|
|
352
|
+
@Text.setter
|
|
353
|
+
def Text(self, value: str):
|
|
354
|
+
if "text" not in self.ClassName.lower():
|
|
355
|
+
raise AttributeError("Text is not available on this instance.")
|
|
356
|
+
self._write_rbx_string(self.raw_address + Offsets["Text"], str(value))
|
|
357
|
+
|
|
160
358
|
# humanoid props #
|
|
161
359
|
@property
|
|
162
360
|
def WalkSpeed(self):
|
|
@@ -165,12 +363,28 @@ class RBXInstance:
|
|
|
165
363
|
|
|
166
364
|
return self.memory_module.read_float(self.raw_address + Offsets["WalkSpeed"])
|
|
167
365
|
|
|
366
|
+
@WalkSpeed.setter
|
|
367
|
+
def WalkSpeed(self, value: float):
|
|
368
|
+
if self.ClassName != "Humanoid":
|
|
369
|
+
raise AttributeError("WalkSpeed is only available on Humanoid instances.")
|
|
370
|
+
self._ensure_writable()
|
|
371
|
+
|
|
372
|
+
self.memory_module.write_float(self.raw_address + Offsets["WalkSpeed"], float(value))
|
|
373
|
+
|
|
168
374
|
@property
|
|
169
375
|
def JumpPower(self):
|
|
170
376
|
if self.ClassName != "Humanoid":
|
|
171
377
|
return None
|
|
172
378
|
|
|
173
379
|
return self.memory_module.read_float(self.raw_address + Offsets["JumpPower"])
|
|
380
|
+
|
|
381
|
+
@JumpPower.setter
|
|
382
|
+
def JumpPower(self, value: float):
|
|
383
|
+
if self.ClassName != "Humanoid":
|
|
384
|
+
raise AttributeError("JumpPower is only available on Humanoid instances.")
|
|
385
|
+
self._ensure_writable()
|
|
386
|
+
|
|
387
|
+
self.memory_module.write_float(self.raw_address + Offsets["JumpPower"], float(value))
|
|
174
388
|
|
|
175
389
|
@property
|
|
176
390
|
def Health(self):
|
|
@@ -179,6 +393,14 @@ class RBXInstance:
|
|
|
179
393
|
|
|
180
394
|
return self.memory_module.read_float(self.raw_address + Offsets["Health"])
|
|
181
395
|
|
|
396
|
+
@Health.setter
|
|
397
|
+
def Health(self, value: float):
|
|
398
|
+
if self.ClassName != "Humanoid":
|
|
399
|
+
raise AttributeError("Health is only available on Humanoid instances.")
|
|
400
|
+
self._ensure_writable()
|
|
401
|
+
|
|
402
|
+
self.memory_module.write_float(self.raw_address + Offsets["Health"], float(value))
|
|
403
|
+
|
|
182
404
|
@property
|
|
183
405
|
def MaxHealth(self):
|
|
184
406
|
if self.ClassName != "Humanoid":
|
|
@@ -186,6 +408,14 @@ class RBXInstance:
|
|
|
186
408
|
|
|
187
409
|
return self.memory_module.read_float(self.raw_address + Offsets["MaxHealth"])
|
|
188
410
|
|
|
411
|
+
@MaxHealth.setter
|
|
412
|
+
def MaxHealth(self, value: float):
|
|
413
|
+
if self.ClassName != "Humanoid":
|
|
414
|
+
raise AttributeError("MaxHealth is only available on Humanoid instances.")
|
|
415
|
+
self._ensure_writable()
|
|
416
|
+
|
|
417
|
+
self.memory_module.write_float(self.raw_address + Offsets["MaxHealth"], float(value))
|
|
418
|
+
|
|
189
419
|
# model props #
|
|
190
420
|
@property
|
|
191
421
|
def PrimaryPart(self):
|
|
@@ -197,6 +427,22 @@ class RBXInstance:
|
|
|
197
427
|
return None
|
|
198
428
|
|
|
199
429
|
return RBXInstance(parent_pointer, self.memory_module)
|
|
430
|
+
|
|
431
|
+
@PrimaryPart.setter
|
|
432
|
+
def PrimaryPart(self, value):
|
|
433
|
+
if self.ClassName != "Model":
|
|
434
|
+
raise AttributeError("PrimaryPart is only available on Model instances.")
|
|
435
|
+
self._ensure_writable()
|
|
436
|
+
|
|
437
|
+
if value is None:
|
|
438
|
+
target = 0
|
|
439
|
+
elif isinstance(value, RBXInstance):
|
|
440
|
+
target = value.raw_address
|
|
441
|
+
elif isinstance(value, int):
|
|
442
|
+
target = value
|
|
443
|
+
else:
|
|
444
|
+
raise TypeError("PrimaryPart must be set to an RBXInstance, int address, or None.")
|
|
445
|
+
self.memory_module.write_long(self.raw_address + Offsets["PrimaryPart"], target)
|
|
200
446
|
|
|
201
447
|
# functions #
|
|
202
448
|
def GetChildren(self):
|
|
@@ -301,6 +547,10 @@ class PlayerClass(RBXInstance):
|
|
|
301
547
|
def DisplayName(self):
|
|
302
548
|
return self.memory_module.read_string(self.raw_address + Offsets["DisplayName"])
|
|
303
549
|
|
|
550
|
+
@DisplayName.setter
|
|
551
|
+
def DisplayName(self, value: str):
|
|
552
|
+
self._write_rbx_string(self.raw_address + Offsets["DisplayName"], str(value))
|
|
553
|
+
|
|
304
554
|
@property
|
|
305
555
|
def UserId(self):
|
|
306
556
|
return self.memory_module.read_long(self.raw_address + Offsets["UserId"])
|
|
@@ -330,16 +580,34 @@ class CameraClass(RBXInstance):
|
|
|
330
580
|
@property
|
|
331
581
|
def FieldOfView(self):
|
|
332
582
|
return self.FieldOfViewRadians * (180/math.pi)
|
|
583
|
+
|
|
584
|
+
@FieldOfView.setter
|
|
585
|
+
def FieldOfView(self, value: float):
|
|
586
|
+
self.FieldOfViewRadians = float(value) * (math.pi / 180)
|
|
333
587
|
|
|
334
588
|
@property
|
|
335
589
|
def FieldOfViewRadians(self):
|
|
336
590
|
return self.memory_module.read_float(self.raw_address + Offsets["FOV"])
|
|
591
|
+
|
|
592
|
+
@FieldOfViewRadians.setter
|
|
593
|
+
def FieldOfViewRadians(self, value: float):
|
|
594
|
+
self._ensure_writable()
|
|
595
|
+
self.memory_module.write_float(self.raw_address + Offsets["FOV"], float(value))
|
|
337
596
|
|
|
338
597
|
@property
|
|
339
598
|
def ViewportSize(self):
|
|
340
599
|
SizeData = self.memory_module.read_floats(self.raw_address + Offsets["ViewportSize"], 2)
|
|
341
600
|
return Vector2(*SizeData)
|
|
342
601
|
|
|
602
|
+
@ViewportSize.setter
|
|
603
|
+
def ViewportSize(self, value):
|
|
604
|
+
vec = self._as_vector2(value, "ViewportSize")
|
|
605
|
+
self._ensure_writable()
|
|
606
|
+
self.memory_module.write_floats(
|
|
607
|
+
self.raw_address + Offsets["ViewportSize"],
|
|
608
|
+
(vec.X, vec.Y)
|
|
609
|
+
)
|
|
610
|
+
|
|
343
611
|
# Service #
|
|
344
612
|
class ServiceBase:
|
|
345
613
|
def __init__(self):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robloxmemoryapi
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary: Python Library that abstracts reading data from the Roblox DataModel
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Python Library that abstracts reading and writing data from the Roblox DataModel
|
|
5
5
|
Author-email: upio <notpoiu@users.noreply.github.com>, mstudio45 <mstudio45@users.noreply.github.com>, ActualMasterOogway <ActualMasterOogway@users.noreply.github.com>
|
|
6
6
|
License: Copyright 2025 upio, mstudio45, master oogway
|
|
7
7
|
|
|
@@ -58,12 +58,26 @@ from robloxmemoryapi import RobloxGameClient
|
|
|
58
58
|
client = RobloxGameClient()
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
To request write access (e.g. to patch values), pass `allow_write=True` when creating the client.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
client = RobloxGameClient(allow_write=True)
|
|
65
|
+
```
|
|
66
|
+
|
|
61
67
|
Access the data model:
|
|
62
68
|
|
|
63
69
|
```python
|
|
64
70
|
game = client.DataModel
|
|
65
71
|
```
|
|
66
72
|
|
|
73
|
+
Kill the local player:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# (requires allow_write=True when creating the client)
|
|
77
|
+
# allow_write may be detected by roblox. It is disabled by default.
|
|
78
|
+
game.Players.LocalPlayer.Character.Humanoid.Health = 0
|
|
79
|
+
```
|
|
80
|
+
|
|
67
81
|
Get the local player's name:
|
|
68
82
|
|
|
69
83
|
```python
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi/utils/rbx/datastructures.py
RENAMED
|
File without changes
|
|
File without changes
|
{robloxmemoryapi-0.0.2 → robloxmemoryapi-0.0.4}/src/robloxmemoryapi.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|