robloxmemoryapi 0.2.1__tar.gz → 0.2.2.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.
- {robloxmemoryapi-0.2.1/src/robloxmemoryapi.egg-info → robloxmemoryapi-0.2.2.1}/PKG-INFO +1 -1
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/pyproject.toml +1 -1
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/__init__.py +14 -1
- robloxmemoryapi-0.2.2.1/src/robloxmemoryapi/utils/offsets.py +37 -0
- robloxmemoryapi-0.2.2.1/src/robloxmemoryapi/utils/rbx/fflags.py +197 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1/src/robloxmemoryapi.egg-info}/PKG-INFO +1 -1
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi.egg-info/SOURCES.txt +1 -0
- robloxmemoryapi-0.2.1/src/robloxmemoryapi/utils/offsets.py +0 -22
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/LICENSE.md +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/README.md +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/setup.cfg +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/__init__.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/luau/__init__.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/luau/parser.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/memory.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/rbx/__init__.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/rbx/bytecode/decryptor.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/rbx/bytecode/encryptor.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/rbx/datastructures.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/rbx/instance.py +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi.egg-info/dependency_links.txt +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi.egg-info/requires.txt +0 -0
- {robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robloxmemoryapi
|
|
3
|
-
Version: 0.2.1
|
|
3
|
+
Version: 0.2.2.1
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "robloxmemoryapi"
|
|
7
|
-
version = "0.2.1"
|
|
7
|
+
version = "0.2.2.1"
|
|
8
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"
|
|
@@ -80,10 +80,23 @@ class RobloxGameClient:
|
|
|
80
80
|
|
|
81
81
|
self.memory_module = EvasiveProcess(self.pid, desired_access)
|
|
82
82
|
self.failed = False
|
|
83
|
+
self._fflags = None
|
|
83
84
|
|
|
84
85
|
def close(self):
|
|
85
86
|
self.memory_module.close()
|
|
86
87
|
|
|
88
|
+
@property
|
|
89
|
+
def FFlags(self):
|
|
90
|
+
if platform.system() != "Windows":
|
|
91
|
+
raise RuntimeError("This module is only compatible with Windows.")
|
|
92
|
+
elif self.failed:
|
|
93
|
+
raise RuntimeError("There was an error while getting access to memory. Please try again later.")
|
|
94
|
+
|
|
95
|
+
if self._fflags is None:
|
|
96
|
+
from .utils.rbx.fflags import FFlagManager
|
|
97
|
+
self._fflags = FFlagManager(self.memory_module)
|
|
98
|
+
return self._fflags
|
|
99
|
+
|
|
87
100
|
@property
|
|
88
101
|
def DataModel(self):
|
|
89
102
|
if platform.system() != "Windows":
|
|
@@ -92,4 +105,4 @@ class RobloxGameClient:
|
|
|
92
105
|
raise RuntimeError("There was an error while getting access to memory. Please try again later.")
|
|
93
106
|
|
|
94
107
|
from .utils.rbx.instance import DataModel
|
|
95
|
-
return DataModel(self.memory_module)
|
|
108
|
+
return DataModel(self.memory_module)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
BASE_URL = "https://imtheo.lol/Offsets"
|
|
4
|
+
|
|
5
|
+
# Offsets
|
|
6
|
+
OffsetsRequest = requests.get(f"{BASE_URL}/Offsets.json")
|
|
7
|
+
|
|
8
|
+
# Legacy Offsets
|
|
9
|
+
OldOffsetsRequest = requests.get("https://offsets.ntgetwritewatch.workers.dev/offsets.json")
|
|
10
|
+
try:
|
|
11
|
+
OldOffsets = OldOffsetsRequest.json()
|
|
12
|
+
except:
|
|
13
|
+
OldOffsets = {}
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
Offsets = OffsetsRequest.json()["Offsets"]
|
|
17
|
+
except:
|
|
18
|
+
Offsets = {}
|
|
19
|
+
|
|
20
|
+
# Handle non-existant offsets
|
|
21
|
+
try:
|
|
22
|
+
Offsets["Camera"]["ViewportSize"] = int(OldOffsets["ViewportSize"], 16)
|
|
23
|
+
except:
|
|
24
|
+
Offsets["Camera"]["ViewportSize"] = 0x6AD28F
|
|
25
|
+
|
|
26
|
+
# FFlag offsets (lazily loaded)
|
|
27
|
+
_fflag_data = None
|
|
28
|
+
_fflag_offsets = None
|
|
29
|
+
|
|
30
|
+
def get_fflag_offsets() -> dict:
|
|
31
|
+
global _fflag_data, _fflag_offsets
|
|
32
|
+
if _fflag_data is None:
|
|
33
|
+
resp = requests.get(f"{BASE_URL}/FFlags.json")
|
|
34
|
+
resp.raise_for_status()
|
|
35
|
+
_fflag_data = resp.json()
|
|
36
|
+
_fflag_offsets = _fflag_data["FFlagOffsets"]["FFlags"]
|
|
37
|
+
return _fflag_offsets
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from ..offsets import get_fflag_offsets
|
|
2
|
+
|
|
3
|
+
_MODULE_SIZE_LIMIT = 0x20000000
|
|
4
|
+
|
|
5
|
+
# FLog level byte → display name.
|
|
6
|
+
_FLOG_LEVELS = {
|
|
7
|
+
2: "Error",
|
|
8
|
+
3: "Warning",
|
|
9
|
+
4: "Info",
|
|
10
|
+
5: "Debug",
|
|
11
|
+
6: "Verbose",
|
|
12
|
+
255: "None",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_VALID_FLOG_LEVELS = set(_FLOG_LEVELS.keys())
|
|
16
|
+
|
|
17
|
+
_FLOG_LEVELS_REV = {v: k for k, v in _FLOG_LEVELS.items()}
|
|
18
|
+
|
|
19
|
+
def _decode_flog(raw_int: int) -> str:
|
|
20
|
+
level_byte = (raw_int >> 8) & 0xFF
|
|
21
|
+
param_byte = raw_int & 0xFF
|
|
22
|
+
level_name = _FLOG_LEVELS.get(level_byte, str(level_byte))
|
|
23
|
+
return f"{level_name},{param_byte}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _encode_flog(value: str) -> int:
|
|
27
|
+
parts = value.split(",", 1)
|
|
28
|
+
level_name = parts[0].strip()
|
|
29
|
+
param = int(parts[1].strip()) if len(parts) > 1 else 0
|
|
30
|
+
level_byte = _FLOG_LEVELS_REV.get(level_name)
|
|
31
|
+
if level_byte is None:
|
|
32
|
+
level_byte = int(level_name)
|
|
33
|
+
return (level_byte << 8) | (param & 0xFF)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FFlag:
|
|
37
|
+
__slots__ = ("name", "type", "value", "offset")
|
|
38
|
+
|
|
39
|
+
def __init__(self, name: str, flag_type: str, value, offset: int):
|
|
40
|
+
self.name = name
|
|
41
|
+
self.type = flag_type # "bool", "int", or "string"
|
|
42
|
+
self.value = value
|
|
43
|
+
self.offset = offset
|
|
44
|
+
|
|
45
|
+
def __repr__(self):
|
|
46
|
+
return f"FFlag(name={self.name!r}, type={self.type!r}, value={self.value!r})"
|
|
47
|
+
|
|
48
|
+
class FFlagManager:
|
|
49
|
+
def __init__(self, memory_module):
|
|
50
|
+
self._mem = memory_module
|
|
51
|
+
self._base = memory_module.base
|
|
52
|
+
self._offsets = None
|
|
53
|
+
|
|
54
|
+
def _ensure_offsets(self):
|
|
55
|
+
if self._offsets is None:
|
|
56
|
+
self._offsets = get_fflag_offsets()
|
|
57
|
+
|
|
58
|
+
def _is_module_ptr(self, ptr: int) -> bool:
|
|
59
|
+
return self._base <= ptr < self._base + _MODULE_SIZE_LIMIT
|
|
60
|
+
|
|
61
|
+
def _reflect(self, name: str, offset: int):
|
|
62
|
+
addr = self._base + offset
|
|
63
|
+
raw = self._mem.read(addr, 32)
|
|
64
|
+
if len(raw) < 32:
|
|
65
|
+
return "unknown", None
|
|
66
|
+
|
|
67
|
+
ptr_at_8 = int.from_bytes(raw[8:16], "little")
|
|
68
|
+
|
|
69
|
+
if self._is_module_ptr(ptr_at_8):
|
|
70
|
+
try:
|
|
71
|
+
name_in_mem = self._mem.read_raw_string(ptr_at_8, 256)
|
|
72
|
+
except OSError:
|
|
73
|
+
name_in_mem = ""
|
|
74
|
+
|
|
75
|
+
val_at_10 = int.from_bytes(raw[16:20], "little")
|
|
76
|
+
|
|
77
|
+
if name_in_mem and val_at_10 == len(name_in_mem):
|
|
78
|
+
return "bool", bool(raw[0])
|
|
79
|
+
else:
|
|
80
|
+
raw_int = int.from_bytes(raw[0:4], "little")
|
|
81
|
+
level_byte = (raw_int >> 8) & 0xFF
|
|
82
|
+
if level_byte in _VALID_FLOG_LEVELS:
|
|
83
|
+
return "flog", _decode_flog(raw_int)
|
|
84
|
+
else:
|
|
85
|
+
return "int", int.from_bytes(raw[0:4], "little", signed=True)
|
|
86
|
+
else:
|
|
87
|
+
str_len = int.from_bytes(raw[16:24], "little")
|
|
88
|
+
str_cap = int.from_bytes(raw[24:32], "little")
|
|
89
|
+
|
|
90
|
+
if str_cap == 15 and 0 <= str_len <= 15:
|
|
91
|
+
return "string", self._mem.read_string(addr)
|
|
92
|
+
|
|
93
|
+
if (str_cap >= 16 and str_cap < 0x100000
|
|
94
|
+
and 0 < str_len <= str_cap):
|
|
95
|
+
ptr_at_0 = int.from_bytes(raw[0:8], "little")
|
|
96
|
+
if ptr_at_0 > 0x10000:
|
|
97
|
+
return "string", self._mem.read_string(addr)
|
|
98
|
+
|
|
99
|
+
return "int", int.from_bytes(raw[0:4], "little", signed=True)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def offsets(self) -> dict:
|
|
103
|
+
self._ensure_offsets()
|
|
104
|
+
return dict(self._offsets)
|
|
105
|
+
|
|
106
|
+
def get(self, name: str) -> FFlag | None:
|
|
107
|
+
self._ensure_offsets()
|
|
108
|
+
offset = self._offsets.get(name)
|
|
109
|
+
if offset is None:
|
|
110
|
+
return None
|
|
111
|
+
flag_type, value = self._reflect(name, offset)
|
|
112
|
+
return FFlag(name, flag_type, value, offset)
|
|
113
|
+
|
|
114
|
+
def get_many(self, *names: str) -> dict[str, FFlag]:
|
|
115
|
+
self._ensure_offsets()
|
|
116
|
+
out = {}
|
|
117
|
+
for n in names:
|
|
118
|
+
flag = self.get(n)
|
|
119
|
+
if flag is not None:
|
|
120
|
+
out[n] = flag
|
|
121
|
+
return out
|
|
122
|
+
|
|
123
|
+
def get_all(self) -> dict[str, FFlag]:
|
|
124
|
+
self._ensure_offsets()
|
|
125
|
+
out = {}
|
|
126
|
+
for name, offset in self._offsets.items():
|
|
127
|
+
try:
|
|
128
|
+
flag_type, value = self._reflect(name, offset)
|
|
129
|
+
out[name] = FFlag(name, flag_type, value, offset)
|
|
130
|
+
except OSError:
|
|
131
|
+
pass
|
|
132
|
+
return out
|
|
133
|
+
|
|
134
|
+
def set(self, name: str, value) -> FFlag:
|
|
135
|
+
self._ensure_offsets()
|
|
136
|
+
offset = self._offsets.get(name)
|
|
137
|
+
if offset is None:
|
|
138
|
+
raise KeyError(name)
|
|
139
|
+
|
|
140
|
+
flag_type, _ = self._reflect(name, offset)
|
|
141
|
+
addr = self._base + offset
|
|
142
|
+
|
|
143
|
+
if flag_type == "bool":
|
|
144
|
+
if not isinstance(value, (bool, int)):
|
|
145
|
+
raise TypeError(f"Expected bool for flag '{name}', got {type(value).__name__}")
|
|
146
|
+
self._mem.write_bool(addr, bool(value))
|
|
147
|
+
|
|
148
|
+
elif flag_type == "int":
|
|
149
|
+
if not isinstance(value, int):
|
|
150
|
+
raise TypeError(f"Expected int for flag '{name}', got {type(value).__name__}")
|
|
151
|
+
self._mem.write_int(addr, value)
|
|
152
|
+
|
|
153
|
+
elif flag_type == "string":
|
|
154
|
+
if not isinstance(value, str):
|
|
155
|
+
raise TypeError(f"Expected str for flag '{name}', got {type(value).__name__}")
|
|
156
|
+
|
|
157
|
+
self._mem.write_string(addr, str(value))
|
|
158
|
+
|
|
159
|
+
elif flag_type == "flog":
|
|
160
|
+
if not isinstance(value, (str, int)):
|
|
161
|
+
raise TypeError(f"Expected str or int for flag '{name}', got {type(value).__name__}")
|
|
162
|
+
|
|
163
|
+
self._mem.write_int(addr, _encode_flog(str(value)) if isinstance(value, str) else value)
|
|
164
|
+
else:
|
|
165
|
+
raise RuntimeError(f"Cannot write to flag with unknown type '{flag_type}'")
|
|
166
|
+
|
|
167
|
+
new_type, new_value = self._reflect(name, offset)
|
|
168
|
+
return FFlag(name, new_type, new_value, offset)
|
|
169
|
+
|
|
170
|
+
def __getitem__(self, name: str) -> FFlag:
|
|
171
|
+
flag = self.get(name)
|
|
172
|
+
if flag is None:
|
|
173
|
+
raise KeyError(name)
|
|
174
|
+
return flag
|
|
175
|
+
|
|
176
|
+
def __getattr__(self, name) -> FFlag:
|
|
177
|
+
flag = self.get(name)
|
|
178
|
+
if flag is None:
|
|
179
|
+
raise AttributeError(f"FFlag '{name}' not found")
|
|
180
|
+
return flag
|
|
181
|
+
|
|
182
|
+
def __setitem__(self, name: str, value):
|
|
183
|
+
self.set(name, value)
|
|
184
|
+
|
|
185
|
+
def __setattr__(self, name: str, value):
|
|
186
|
+
if name.startswith("_"):
|
|
187
|
+
object.__setattr__(self, name, value)
|
|
188
|
+
else:
|
|
189
|
+
self.set(name, value)
|
|
190
|
+
|
|
191
|
+
def __contains__(self, name: str) -> bool:
|
|
192
|
+
self._ensure_offsets()
|
|
193
|
+
return name in self._offsets
|
|
194
|
+
|
|
195
|
+
def __repr__(self):
|
|
196
|
+
self._ensure_offsets()
|
|
197
|
+
return f"FFlagManager({len(self._offsets)} flags)"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robloxmemoryapi
|
|
3
|
-
Version: 0.2.1
|
|
3
|
+
Version: 0.2.2.1
|
|
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
|
|
@@ -14,6 +14,7 @@ src/robloxmemoryapi/utils/luau/__init__.py
|
|
|
14
14
|
src/robloxmemoryapi/utils/luau/parser.py
|
|
15
15
|
src/robloxmemoryapi/utils/rbx/__init__.py
|
|
16
16
|
src/robloxmemoryapi/utils/rbx/datastructures.py
|
|
17
|
+
src/robloxmemoryapi/utils/rbx/fflags.py
|
|
17
18
|
src/robloxmemoryapi/utils/rbx/instance.py
|
|
18
19
|
src/robloxmemoryapi/utils/rbx/bytecode/decryptor.py
|
|
19
20
|
src/robloxmemoryapi/utils/rbx/bytecode/encryptor.py
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
|
|
3
|
-
# Offsets
|
|
4
|
-
OffsetsRequest = requests.get("https://imtheo.lol/Offsets/Offsets.json")
|
|
5
|
-
|
|
6
|
-
# Legacy Offsets
|
|
7
|
-
OldOffsetsRequest = requests.get("https://offsets.ntgetwritewatch.workers.dev/offsets.json")
|
|
8
|
-
try:
|
|
9
|
-
OldOffsets = OldOffsetsRequest.json()
|
|
10
|
-
except:
|
|
11
|
-
OldOffsets = {}
|
|
12
|
-
|
|
13
|
-
try:
|
|
14
|
-
Offsets = OffsetsRequest.json()["Offsets"]
|
|
15
|
-
except:
|
|
16
|
-
Offsets = {}
|
|
17
|
-
|
|
18
|
-
# Handle non-existant offsets
|
|
19
|
-
try:
|
|
20
|
-
Offsets["Camera"]["ViewportSize"] = int(OldOffsets["ViewportSize"], 16)
|
|
21
|
-
except:
|
|
22
|
-
Offsets["Camera"]["ViewportSize"] = 0x6AD28F
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/luau/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi/utils/rbx/datastructures.py
RENAMED
|
File without changes
|
|
File without changes
|
{robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{robloxmemoryapi-0.2.1 → robloxmemoryapi-0.2.2.1}/src/robloxmemoryapi.egg-info/top_level.txt
RENAMED
|
File without changes
|