robloxmemoryapi 0.0.1__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.
@@ -0,0 +1,7 @@
1
+ Copyright 2025 upio, mstudio45, master oogway
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: robloxmemoryapi
3
+ Version: 0.0.1
4
+ Summary: Python Library that abstracts reading data from the Roblox DataModel
5
+ Author-email: upio <notpoiu@users.noreply.github.com>, mstudio45 <mstudio45@users.noreply.github.com>, ActualMasterOogway <ActualMasterOogway@users.noreply.github.com>
6
+ License: Copyright 2025 upio, mstudio45, master oogway
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+
14
+ Project-URL: Homepage, https://github.com/notpoiu/RobloxMemoryAPI
15
+ Project-URL: Issues, https://github.com/notpoiu/RobloxMemoryAPI/issues
16
+ Keywords: roblox,memory,windows
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: Microsoft :: Windows
20
+ Classifier: Development Status :: 3 - Alpha
21
+ Classifier: Intended Audience :: Developers
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE.md
26
+ Requires-Dist: requests>=2.0
27
+ Dynamic: license-file
28
+
29
+ # RobloxMemoryAPI
30
+
31
+ A Python library that is _hopefully stealthy_ and abstracts externally reading memory to get datamodel information from the roblox game client.
32
+
33
+ This was made by [upio](https://github.com/notpoiu), [mstudio45](https://github.com/mstudio45), and [Master Oogway](https://github.com/ActualMasterOogway) and created for the [Dig Macro](https://github.com/mstudio45/digmacro) project (external mode and not the computer vision mode).
34
+
35
+ ## Installation
36
+
37
+ PyPI:
38
+
39
+ ```bash
40
+ pip install robloxmemoryapi
41
+ ```
42
+
43
+ Development (editable install from source):
44
+
45
+ ```bash
46
+ pip install -e .
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ An example script can be found in [example.py](example.py). If running from the repo, use the editable install above so `import robloxmemoryapi` resolves the `src` package.
52
+
53
+ Import the library and create a client instance:
54
+
55
+ ```python
56
+ from robloxmemoryapi import RobloxGameClient
57
+
58
+ client = RobloxGameClient()
59
+ ```
60
+
61
+ Access the data model:
62
+
63
+ ```python
64
+ game = client.DataModel
65
+ ```
66
+
67
+ Get the local player's name:
68
+
69
+ ```python
70
+ print("Player Name:", game.Players.LocalPlayer.Name)
71
+ ```
72
+
73
+ ## License
74
+
75
+ This project is licensed under the MIT License.
@@ -0,0 +1,47 @@
1
+ # RobloxMemoryAPI
2
+
3
+ A Python library that is _hopefully stealthy_ and abstracts externally reading memory to get datamodel information from the roblox game client.
4
+
5
+ This was made by [upio](https://github.com/notpoiu), [mstudio45](https://github.com/mstudio45), and [Master Oogway](https://github.com/ActualMasterOogway) and created for the [Dig Macro](https://github.com/mstudio45/digmacro) project (external mode and not the computer vision mode).
6
+
7
+ ## Installation
8
+
9
+ PyPI:
10
+
11
+ ```bash
12
+ pip install robloxmemoryapi
13
+ ```
14
+
15
+ Development (editable install from source):
16
+
17
+ ```bash
18
+ pip install -e .
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ An example script can be found in [example.py](example.py). If running from the repo, use the editable install above so `import robloxmemoryapi` resolves the `src` package.
24
+
25
+ Import the library and create a client instance:
26
+
27
+ ```python
28
+ from robloxmemoryapi import RobloxGameClient
29
+
30
+ client = RobloxGameClient()
31
+ ```
32
+
33
+ Access the data model:
34
+
35
+ ```python
36
+ game = client.DataModel
37
+ ```
38
+
39
+ Get the local player's name:
40
+
41
+ ```python
42
+ print("Player Name:", game.Players.LocalPlayer.Name)
43
+ ```
44
+
45
+ ## License
46
+
47
+ This project is licensed under the MIT License.
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "robloxmemoryapi"
7
+ version = "0.0.1"
8
+ description = "Python Library that abstracts reading data from the Roblox DataModel"
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ requires-python = ">=3.9"
11
+ license = { file = "LICENSE.md" }
12
+ authors = [
13
+ { name = "upio", email = "notpoiu@users.noreply.github.com" },
14
+ { name = "mstudio45", email = "mstudio45@users.noreply.github.com" },
15
+ { name = "ActualMasterOogway", email = "ActualMasterOogway@users.noreply.github.com" },
16
+ ]
17
+ keywords = ["roblox", "memory", "windows"]
18
+ classifiers = [
19
+ "Programming Language :: Python :: 3",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: Microsoft :: Windows",
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Developers",
24
+ "Topic :: Software Development :: Libraries",
25
+ ]
26
+ dependencies = [
27
+ "requests>=2.0",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/notpoiu/RobloxMemoryAPI"
32
+ Issues = "https://github.com/notpoiu/RobloxMemoryAPI/issues"
33
+
34
+ [tool.setuptools]
35
+ package-dir = {"" = "src"}
36
+ include-package-data = true
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["src"]
40
+
41
+ [tool.setuptools.package-data]
42
+ robloxmemoryapi = ["data/*.json"]
43
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,87 @@
1
+ import platform
2
+ import math
3
+
4
+ __all__ = ["RobloxRandom", "RobloxGameClient", "__version__"]
5
+ __version__ = "0.1.0"
6
+
7
+
8
+ class RobloxRandom:
9
+ MULT = 6364136223846793005
10
+ INC = 105
11
+ MASK64 = (1 << 64) - 1
12
+
13
+ def __init__(self, seed):
14
+ s = math.floor(seed)
15
+
16
+ self._state = 0
17
+ self._inc = RobloxRandom.INC
18
+ self._next_internal() # warm-up #1
19
+ self._state = (self._state + s) & RobloxRandom.MASK64
20
+ self._next_internal() # warm-up #2
21
+
22
+ def _next_internal(self):
23
+ old = self._state
24
+ self._state = (old * RobloxRandom.MULT + self._inc) & RobloxRandom.MASK64
25
+ x = ((old >> 18) ^ old) >> 27
26
+ r = old >> 59
27
+ return ((x >> r) | (x << ((32 - r) & 31))) & 0xFFFFFFFF
28
+
29
+ def _next_fraction64(self):
30
+ lo = self._next_internal()
31
+ hi = self._next_internal()
32
+ bits = (hi << 32) | lo
33
+ return bits / 2**64
34
+
35
+ def NextNumber(self, minimum=0.0, maximum=1.0):
36
+ frac = self._next_fraction64()
37
+ return minimum + frac * (maximum - minimum)
38
+
39
+ def NextInteger(self, a, b=None):
40
+ if b is None:
41
+ u = a
42
+ r = self._next_internal()
43
+ return ((u * r) >> 32) + 1
44
+ else:
45
+ lo, hi = (a, b) if a <= b else (b, a)
46
+ u = hi - lo + 1
47
+ r = self._next_internal()
48
+ return ((u * r) >> 32) + lo
49
+
50
+
51
+ class RobloxGameClient:
52
+ def __init__(self, pid: int = None, process_name: str = "RobloxPlayerBeta.exe"):
53
+ if platform.system() != "Windows":
54
+ self.failed = True
55
+ return
56
+
57
+ from .utils.memory import (
58
+ EvasiveProcess,
59
+ PROCESS_QUERY_INFORMATION,
60
+ PROCESS_VM_READ,
61
+ get_pid_by_name,
62
+ )
63
+
64
+ if pid is None:
65
+ self.pid = get_pid_by_name(process_name)
66
+ else:
67
+ self.pid = pid
68
+
69
+ if self.pid is None or self.pid == 0:
70
+ raise ValueError("Failed to get PID.")
71
+
72
+ self.memory_module = EvasiveProcess(self.pid, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION)
73
+ self.failed = False
74
+
75
+ def close(self):
76
+ self.memory_module.close()
77
+
78
+ @property
79
+ def DataModel(self):
80
+ if platform.system() != "Windows":
81
+ raise RuntimeError("This module is only compatible with Windows.")
82
+ elif self.failed:
83
+ raise RuntimeError("There was an error while getting access to memory. Please try again later.")
84
+
85
+ from .utils.rbx.instance import DataModel
86
+ return DataModel(self.memory_module)
87
+
@@ -0,0 +1,6 @@
1
+ {
2
+ "Text": "0xC10",
3
+ "Character": "0x328",
4
+ "PrimaryPart": "0x268"
5
+ }
6
+
@@ -0,0 +1,2 @@
1
+ # Utility subpackage for memory access and offsets
2
+
@@ -0,0 +1,275 @@
1
+ import ctypes
2
+ import struct
3
+ from ctypes import wintypes
4
+
5
+ ntdll = ctypes.WinDLL('ntdll.dll')
6
+ psapi = ctypes.WinDLL('psapi.dll')
7
+ kernel32 = ctypes.WinDLL('kernel32.dll')
8
+
9
+ kernel32.VirtualAlloc.restype = wintypes.LPVOID
10
+ kernel32.VirtualAlloc.argtypes = [wintypes.LPVOID, ctypes.c_size_t, wintypes.DWORD, wintypes.DWORD]
11
+ kernel32.GetProcAddress.restype = wintypes.LPVOID
12
+ kernel32.GetProcAddress.argtypes = [wintypes.HMODULE, wintypes.LPCSTR]
13
+
14
+ NTSTATUS = wintypes.LONG
15
+ HANDLE = wintypes.HANDLE
16
+ DWORD = wintypes.DWORD
17
+ LPVOID = wintypes.LPVOID
18
+ HMODULE = wintypes.HMODULE
19
+ BOOL = wintypes.BOOL
20
+
21
+ PROCESS_QUERY_INFORMATION = 0x0400
22
+ PROCESS_VM_READ = 0x0010
23
+ LIST_MODULES_ALL = 0x03
24
+ STATUS_SUCCESS = 0
25
+ MEM_COMMIT = 0x1000
26
+ MEM_RESERVE = 0x2000
27
+ PAGE_EXECUTE_READWRITE = 0x40
28
+ NTDLL_HANDLE = ntdll._handle
29
+
30
+ class CLIENT_ID(ctypes.Structure):
31
+ _fields_ = [
32
+ ("UniqueProcess", HANDLE),
33
+ ("UniqueThread", HANDLE),
34
+ ]
35
+
36
+ class OBJECT_ATTRIBUTES(ctypes.Structure):
37
+ _fields_ = [
38
+ ("Length", wintypes.ULONG),
39
+ ("RootDirectory", HANDLE),
40
+ ("ObjectName", LPVOID),
41
+ ("Attributes", wintypes.ULONG),
42
+ ("SecurityDescriptor", LPVOID),
43
+ ("SecurityQualityOfService", LPVOID),
44
+ ]
45
+
46
+ def get_syscall_number(function_name: str) -> int | None:
47
+ func_address = kernel32.GetProcAddress(NTDLL_HANDLE, function_name.encode('ascii'))
48
+ if not func_address:
49
+ return None
50
+ buffer = (ctypes.c_ubyte * 8).from_address(func_address)
51
+ if tuple(buffer[0:4]) == (0x4c, 0x8b, 0xd1, 0xb8):
52
+ return int.from_bytes(bytes(buffer[4:8]), 'little')
53
+ return None
54
+
55
+ def create_syscall_function(syscall_number, func_prototype):
56
+ assembly_stub = b'\x4C\x8B\xD1' + \
57
+ b'\xB8' + syscall_number.to_bytes(4, 'little') + \
58
+ b'\x0F\x05' + \
59
+ b'\xC3'
60
+
61
+ exec_mem = kernel32.VirtualAlloc(None, len(assembly_stub), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)
62
+ if not exec_mem:
63
+ raise ctypes.WinError(ctypes.get_last_error())
64
+ ctypes.memmove(exec_mem, assembly_stub, len(assembly_stub))
65
+ return func_prototype(exec_mem)
66
+
67
+ NtOpenProcessProto = ctypes.WINFUNCTYPE(
68
+ NTSTATUS,
69
+ ctypes.POINTER(HANDLE),
70
+ DWORD,
71
+ ctypes.POINTER(OBJECT_ATTRIBUTES),
72
+ ctypes.POINTER(CLIENT_ID)
73
+ )
74
+ NtReadVirtualMemoryProto = ctypes.WINFUNCTYPE(
75
+ NTSTATUS,
76
+ HANDLE,
77
+ LPVOID,
78
+ LPVOID,
79
+ ctypes.c_ulong,
80
+ ctypes.POINTER(ctypes.c_ulong)
81
+ )
82
+ NtCloseProto = ctypes.WINFUNCTYPE(NTSTATUS, HANDLE)
83
+
84
+ syscall_id_open = get_syscall_number("NtOpenProcess")
85
+ syscall_id_read = get_syscall_number("NtReadVirtualMemory")
86
+ syscall_id_close = get_syscall_number("NtClose")
87
+
88
+ if not all([syscall_id_open, syscall_id_read, syscall_id_close]):
89
+ raise RuntimeError("Could not find required syscall numbers.")
90
+
91
+ nt_open_process_syscall = create_syscall_function(syscall_id_open, NtOpenProcessProto)
92
+ nt_read_virtual_memory_syscall = create_syscall_function(syscall_id_read, NtReadVirtualMemoryProto)
93
+ nt_close_syscall = create_syscall_function(syscall_id_close, NtCloseProto)
94
+
95
+ psapi.EnumProcessModulesEx.argtypes = [
96
+ HANDLE,
97
+ ctypes.POINTER(HMODULE),
98
+ DWORD,
99
+ ctypes.POINTER(DWORD),
100
+ DWORD
101
+ ]
102
+ psapi.EnumProcessModulesEx.restype = BOOL
103
+
104
+ def _get_module_base(process_handle: HANDLE) -> int:
105
+ try:
106
+ modules_arr_size = 256
107
+ modules_arr = (HMODULE * modules_arr_size)()
108
+ needed = DWORD(0)
109
+ psapi.EnumProcessModulesEx(
110
+ process_handle,
111
+ modules_arr,
112
+ ctypes.sizeof(modules_arr),
113
+ ctypes.byref(needed),
114
+ LIST_MODULES_ALL
115
+ )
116
+ if needed.value > ctypes.sizeof(modules_arr):
117
+ new_size = needed.value // ctypes.sizeof(HMODULE)
118
+ modules_arr = (HMODULE * new_size)()
119
+ success = psapi.EnumProcessModulesEx(
120
+ process_handle,
121
+ modules_arr,
122
+ ctypes.sizeof(modules_arr),
123
+ ctypes.byref(needed),
124
+ LIST_MODULES_ALL
125
+ )
126
+ if not success:
127
+ return 0
128
+ if needed.value > 0:
129
+ return modules_arr[0] if modules_arr[0] else 0
130
+ return 0
131
+ except Exception as e:
132
+ print(f"An exception occurred in _get_module_base: {e}")
133
+ return 0
134
+
135
+ def get_pid_by_name(process_name: str) -> int:
136
+ class PROCESSENTRY32(ctypes.Structure):
137
+ _fields_ = [
138
+ ("dwSize", DWORD),
139
+ ("cntUsage", DWORD),
140
+ ("th32ProcessID", DWORD),
141
+ ("th32DefaultHeapID", ctypes.POINTER(wintypes.ULONG)),
142
+ ("th32ModuleID", DWORD),
143
+ ("cntThreads", DWORD),
144
+ ("th32ParentProcessID", DWORD),
145
+ ("pcPriClassBase", wintypes.LONG),
146
+ ("dwFlags", DWORD),
147
+ ("szExeFile", wintypes.CHAR * 260)
148
+ ]
149
+ snapshot = kernel32.CreateToolhelp32Snapshot(2, 0)
150
+ if not snapshot or snapshot == wintypes.HANDLE(-1).value:
151
+ raise ctypes.WinError()
152
+ entry = PROCESSENTRY32()
153
+ entry.dwSize = ctypes.sizeof(PROCESSENTRY32)
154
+ try:
155
+ if not kernel32.Process32First(snapshot, ctypes.byref(entry)):
156
+ return 0
157
+ while True:
158
+ if entry.szExeFile.decode('utf-8', errors='ignore') == process_name:
159
+ return entry.th32ProcessID
160
+ if not kernel32.Process32Next(snapshot, ctypes.byref(entry)):
161
+ break
162
+ finally:
163
+ kernel32.CloseHandle(snapshot)
164
+ return 0
165
+
166
+ class EvasiveProcess:
167
+ def __init__(self, pid: int, access: DWORD):
168
+ self.pid = pid
169
+ self.access = access
170
+ self.handle = HANDLE(0)
171
+ self.base = 0
172
+
173
+ object_attributes = OBJECT_ATTRIBUTES()
174
+ client_id = CLIENT_ID()
175
+ client_id.UniqueProcess = HANDLE(pid)
176
+ object_attributes.Length = ctypes.sizeof(OBJECT_ATTRIBUTES)
177
+
178
+ status = nt_open_process_syscall(
179
+ ctypes.byref(self.handle),
180
+ access,
181
+ ctypes.byref(object_attributes),
182
+ ctypes.byref(client_id)
183
+ )
184
+ if status != STATUS_SUCCESS:
185
+ raise ctypes.WinError(f"NtOpenProcess failed with NTSTATUS: 0x{status:X}")
186
+ self.base = _get_module_base(self.handle)
187
+ if self.base == 0:
188
+ self.close()
189
+ raise ConnectionError("Failed to get module base address.")
190
+
191
+ def read(self, address: int, size: int) -> bytes:
192
+ if not self.handle or self.handle.value == 0:
193
+ raise ValueError("Process handle is not valid.")
194
+ buffer = ctypes.create_string_buffer(size)
195
+ bytes_read = ctypes.c_ulong(0)
196
+ status = nt_read_virtual_memory_syscall(
197
+ self.handle,
198
+ LPVOID(address),
199
+ buffer,
200
+ size,
201
+ ctypes.byref(bytes_read)
202
+ )
203
+ if status != STATUS_SUCCESS:
204
+ raise OSError(f"NtReadVirtualMemory failed with NTSTATUS: 0x{status:X}")
205
+ return buffer.raw[:bytes_read.value]
206
+
207
+ # numbers #
208
+ def read_int(self, address: int) -> int:
209
+ buffer = self.read(address, 4)
210
+ return int.from_bytes(buffer, 'little') if len(buffer) == 4 else 0
211
+
212
+ def read_long(self, address: int) -> int:
213
+ buffer = self.read(address, 8)
214
+ return int.from_bytes(buffer, 'little') if len(buffer) == 8 else 0
215
+
216
+ def read_double(self, address: int) -> float:
217
+ try:
218
+ double_bytes = self.read(address, 8)
219
+ return struct.unpack('<d', double_bytes)[0] if len(double_bytes) == 8 else 0.0
220
+ except (OSError, struct.error):
221
+ return 0.0
222
+
223
+ def read_float(self, address: int) -> float:
224
+ try:
225
+ float_bytes = self.read(address, 4)
226
+ return struct.unpack('f', float_bytes)[0] if len(float_bytes) == 4 else 0.0
227
+ except (OSError, struct.error):
228
+ return 0.0
229
+
230
+ def read_floats(self, address: int, amount: int):
231
+ try:
232
+ bulk_float_bytes = self.read(address, 4 * amount)
233
+ floats = []
234
+ for i in range(amount):
235
+ start_range = i * 4
236
+ float_bytes = bulk_float_bytes[start_range:start_range + 4]
237
+
238
+ if len(float_bytes) == 4:
239
+ floats.append(struct.unpack('f', float_bytes)[0])
240
+ else:
241
+ floats.append(0.0)
242
+
243
+ return floats
244
+ except (OSError, struct.error) as e:
245
+ return [0.0]
246
+
247
+ # bool #
248
+ def read_bool(self, address: int) -> bool:
249
+ try:
250
+ bool_byte = self.read(address, 1)
251
+ if not bool_byte: return False
252
+ return bool(int.from_bytes(bool_byte, 'little'))
253
+ except OSError:
254
+ return False
255
+
256
+ # string #
257
+ def read_raw_string(self, address: int, max_length: int = 256) -> str:
258
+ buffer = self.read(address, max_length)
259
+ null_pos = buffer.find(b'\x00')
260
+ valid_bytes = buffer[:null_pos] if null_pos != -1 else buffer
261
+ return valid_bytes.decode('utf-8', errors='ignore')
262
+
263
+ def read_string(self, address: int) -> str:
264
+ string_length = self.read_int(address + 0x10)
265
+ if string_length <= 15:
266
+ return self.read_raw_string(address, string_length)
267
+ else:
268
+ string_data_pointer = self.read_long(address)
269
+ return self.read_raw_string(string_data_pointer, string_length) if string_data_pointer else ""
270
+
271
+ #########
272
+ def close(self):
273
+ if self.handle and self.handle.value != 0:
274
+ nt_close_syscall(self.handle)
275
+ self.handle = HANDLE(0)
@@ -0,0 +1,46 @@
1
+ import json
2
+ import requests
3
+ from importlib import resources
4
+
5
+ Offsets = {}
6
+ def ParseOffsets(*DataSources):
7
+ for _, Data in enumerate(DataSources, start=1):
8
+ for OffsetName in Data:
9
+ try:
10
+ FormattedOffsetName = OffsetName.replace(" ", "")
11
+ OffsetHexadecimalValue = Data[OffsetName]
12
+
13
+ Offsets[FormattedOffsetName] = int(OffsetHexadecimalValue, 16)
14
+ except (ValueError, TypeError):
15
+ pass
16
+
17
+
18
+ OffsetsRequest = requests.get("https://offsets.ntgetwritewatch.workers.dev/offsets.json")
19
+
20
+ try:
21
+ LoadedOffsetsRequest = requests.get(
22
+ "https://raw.githubusercontent.com/notpoiu/RobloxMemoryAPI/refs/heads/main/src/robloxmemoryapi/data/offsets.json"
23
+ )
24
+ LoadedOffsetsRequest.raise_for_status()
25
+
26
+ LoadedOffsets = LoadedOffsetsRequest.json()
27
+ except Exception:
28
+ try:
29
+ with resources.files("robloxmemoryapi.data").joinpath("offsets.json").open("r", encoding="utf-8") as f:
30
+ LoadedOffsets = json.load(f)
31
+ except Exception:
32
+ # Fallback defaults
33
+ LoadedOffsets = {
34
+ "Text": "0xC10",
35
+ "Character": "0x328",
36
+ "PrimaryPart": "0x268",
37
+ }
38
+
39
+ ParseOffsets(LoadedOffsets, OffsetsRequest.json())
40
+
41
+ # CFrame Offsets
42
+ RotationMatriciesLengthBytes = 3 * 3 * 4
43
+
44
+ Offsets["CameraCFrame"] = Offsets["CameraPos"] - RotationMatriciesLengthBytes
45
+ Offsets["CFrame"] = Offsets["Position"] - RotationMatriciesLengthBytes
46
+