robloxmemoryapi 0.3.1__tar.gz → 0.3.2__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.3.1 → robloxmemoryapi-0.3.2}/PKG-INFO +3 -2
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/pyproject.toml +3 -2
- robloxmemoryapi-0.3.2/src/robloxmemoryapi/__init__.py +231 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/_native/memory.cpp +134 -2
- robloxmemoryapi-0.3.2/src/robloxmemoryapi/_version.py +44 -0
- robloxmemoryapi-0.3.2/src/robloxmemoryapi/utils/macos.py +153 -0
- robloxmemoryapi-0.3.2/src/robloxmemoryapi/utils/offsets.py +43 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/instance.py +14 -3
- robloxmemoryapi-0.3.1/src/robloxmemoryapi/__init__.py +0 -112
- robloxmemoryapi-0.3.1/src/robloxmemoryapi/utils/offsets.py +0 -24
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/.github/workflows/publish.yml +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/.gitignore +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/.luaurc +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/CMakeLists.txt +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/LICENSE.md +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/README.md +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/example.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/misc/BytecodeGen.luau +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/rokit.toml +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/_native/__init__.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/__init__.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/luau/__init__.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/luau/parser.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/memory.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/__init__.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/bytecode/decryptor.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/bytecode/encryptor.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/datastructures.py +0 -0
- {robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/fflags.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: robloxmemoryapi
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Python Library that abstracts reading and writing data from the Roblox DataModel
|
|
5
|
-
Keywords: roblox,memory,windows
|
|
5
|
+
Keywords: roblox,memory,windows,macOS
|
|
6
6
|
Author-Email: upio <notpoiu@users.noreply.github.com>, mstudio45 <mstudio45@users.noreply.github.com>, ActualMasterOogway <ActualMasterOogway@users.noreply.github.com>
|
|
7
7
|
License: Copyright 2025 upio, mstudio45, master oogway
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ License: Copyright 2025 upio, mstudio45, master oogway
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
17
|
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
18
19
|
Classifier: Development Status :: 3 - Alpha
|
|
19
20
|
Classifier: Intended Audience :: Developers
|
|
20
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "robloxmemoryapi"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
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"
|
|
@@ -14,11 +14,12 @@ authors = [
|
|
|
14
14
|
{ name = "mstudio45", email = "mstudio45@users.noreply.github.com" },
|
|
15
15
|
{ name = "ActualMasterOogway", email = "ActualMasterOogway@users.noreply.github.com" },
|
|
16
16
|
]
|
|
17
|
-
keywords = ["roblox", "memory", "windows"]
|
|
17
|
+
keywords = ["roblox", "memory", "windows", "macOS"]
|
|
18
18
|
classifiers = [
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
"License :: OSI Approved :: MIT License",
|
|
21
21
|
"Operating System :: Microsoft :: Windows",
|
|
22
|
+
"Operating System :: MacOS",
|
|
22
23
|
"Development Status :: 3 - Alpha",
|
|
23
24
|
"Intended Audience :: Developers",
|
|
24
25
|
"Topic :: Software Development :: Libraries",
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import math
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
from ._version import __version__
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"RobloxRandom",
|
|
12
|
+
"RobloxGameClient",
|
|
13
|
+
"codesign_roblox_macos",
|
|
14
|
+
"codesign_python_macos",
|
|
15
|
+
"__version__",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def codesign_roblox_macos(
|
|
20
|
+
app_path: str | os.PathLike[str] | None = None,
|
|
21
|
+
*,
|
|
22
|
+
silicon_app_path: str | os.PathLike[str] = "/Applications/Roblox.app",
|
|
23
|
+
intel_app_path: str | os.PathLike[str] = "/Applications/RobloxPlayer.app",
|
|
24
|
+
use_sudo: bool = True,
|
|
25
|
+
) -> str:
|
|
26
|
+
"""Ad-hoc sign a macOS Roblox app bundle for local memory access workflows."""
|
|
27
|
+
if platform.system() != "Darwin":
|
|
28
|
+
raise RuntimeError("codesign_roblox_macos is only available on macOS.")
|
|
29
|
+
|
|
30
|
+
if app_path is None:
|
|
31
|
+
app_path = silicon_app_path if platform.machine().lower() == "arm64" else intel_app_path
|
|
32
|
+
|
|
33
|
+
app_path = os.fspath(app_path)
|
|
34
|
+
if not os.path.exists(app_path):
|
|
35
|
+
raise FileNotFoundError(app_path)
|
|
36
|
+
|
|
37
|
+
sudo_prefix = ["sudo"] if use_sudo and os.geteuid() != 0 else []
|
|
38
|
+
subprocess.run(
|
|
39
|
+
sudo_prefix + ["codesign", "--remove-signature", app_path],
|
|
40
|
+
stdout=subprocess.DEVNULL,
|
|
41
|
+
stderr=subprocess.DEVNULL,
|
|
42
|
+
check=False,
|
|
43
|
+
)
|
|
44
|
+
subprocess.run(
|
|
45
|
+
sudo_prefix + ["codesign", "--force", "--deep", "--sign", "-", app_path],
|
|
46
|
+
check=True,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return app_path
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def codesign_python_macos(
|
|
53
|
+
executable_path: str | os.PathLike[str] | None = None,
|
|
54
|
+
*,
|
|
55
|
+
use_sudo: bool = True,
|
|
56
|
+
) -> str:
|
|
57
|
+
"""Ad-hoc sign the Python executable/launcher with the macOS debugger entitlement."""
|
|
58
|
+
if platform.system() != "Darwin":
|
|
59
|
+
raise RuntimeError("codesign_python_macos is only available on macOS.")
|
|
60
|
+
|
|
61
|
+
if executable_path is None:
|
|
62
|
+
executable_path = sys.executable
|
|
63
|
+
|
|
64
|
+
executable_path = os.path.realpath(os.fspath(executable_path))
|
|
65
|
+
if not os.path.exists(executable_path):
|
|
66
|
+
raise FileNotFoundError(executable_path)
|
|
67
|
+
|
|
68
|
+
entitlements = """<?xml version="1.0" encoding="UTF-8"?>
|
|
69
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
70
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
71
|
+
<plist version="1.0">
|
|
72
|
+
<dict>
|
|
73
|
+
<key>com.apple.security.cs.debugger</key>
|
|
74
|
+
<true/>
|
|
75
|
+
<key>com.apple.security.get-task-allow</key>
|
|
76
|
+
<true/>
|
|
77
|
+
<key>SecTaskAccess</key>
|
|
78
|
+
<array><string>allowed</string></array>
|
|
79
|
+
</dict>
|
|
80
|
+
</plist>
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
sudo_prefix = ["sudo"] if use_sudo and os.geteuid() != 0 else []
|
|
84
|
+
with tempfile.NamedTemporaryFile("w", suffix=".plist", delete=False) as entitlement_file:
|
|
85
|
+
entitlement_file.write(entitlements)
|
|
86
|
+
entitlement_path = entitlement_file.name
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
subprocess.run(
|
|
90
|
+
sudo_prefix + ["codesign", "--remove-signature", executable_path],
|
|
91
|
+
stdout=subprocess.DEVNULL,
|
|
92
|
+
stderr=subprocess.DEVNULL,
|
|
93
|
+
check=False,
|
|
94
|
+
)
|
|
95
|
+
subprocess.run(
|
|
96
|
+
sudo_prefix
|
|
97
|
+
+ [
|
|
98
|
+
"codesign",
|
|
99
|
+
"--force",
|
|
100
|
+
"--sign",
|
|
101
|
+
"-",
|
|
102
|
+
"--entitlements",
|
|
103
|
+
entitlement_path,
|
|
104
|
+
executable_path,
|
|
105
|
+
],
|
|
106
|
+
check=True,
|
|
107
|
+
)
|
|
108
|
+
finally:
|
|
109
|
+
try:
|
|
110
|
+
os.unlink(entitlement_path)
|
|
111
|
+
except OSError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
return executable_path
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class RobloxRandom:
|
|
118
|
+
MULT = 6364136223846793005
|
|
119
|
+
INC = 105
|
|
120
|
+
MASK64 = (1 << 64) - 1
|
|
121
|
+
|
|
122
|
+
def __init__(self, seed):
|
|
123
|
+
s = math.floor(seed)
|
|
124
|
+
|
|
125
|
+
self._state = 0
|
|
126
|
+
self._inc = RobloxRandom.INC
|
|
127
|
+
self._next_internal() # warm-up #1
|
|
128
|
+
self._state = (self._state + s) & RobloxRandom.MASK64
|
|
129
|
+
self._next_internal() # warm-up #2
|
|
130
|
+
|
|
131
|
+
def _next_internal(self):
|
|
132
|
+
old = self._state
|
|
133
|
+
self._state = (old * RobloxRandom.MULT + self._inc) & RobloxRandom.MASK64
|
|
134
|
+
x = ((old >> 18) ^ old) >> 27
|
|
135
|
+
r = old >> 59
|
|
136
|
+
return ((x >> r) | (x << ((32 - r) & 31))) & 0xFFFFFFFF
|
|
137
|
+
|
|
138
|
+
def _next_fraction64(self):
|
|
139
|
+
lo = self._next_internal()
|
|
140
|
+
hi = self._next_internal()
|
|
141
|
+
bits = (hi << 32) | lo
|
|
142
|
+
return bits / 2**64
|
|
143
|
+
|
|
144
|
+
def NextNumber(self, minimum=0.0, maximum=1.0):
|
|
145
|
+
frac = self._next_fraction64()
|
|
146
|
+
return minimum + frac * (maximum - minimum)
|
|
147
|
+
|
|
148
|
+
def NextInteger(self, a, b=None):
|
|
149
|
+
if b is None:
|
|
150
|
+
u = a
|
|
151
|
+
r = self._next_internal()
|
|
152
|
+
return ((u * r) >> 32) + 1
|
|
153
|
+
else:
|
|
154
|
+
lo, hi = (a, b) if a <= b else (b, a)
|
|
155
|
+
u = hi - lo + 1
|
|
156
|
+
r = self._next_internal()
|
|
157
|
+
return ((u * r) >> 32) + lo
|
|
158
|
+
|
|
159
|
+
class RobloxGameClient:
|
|
160
|
+
def __init__(
|
|
161
|
+
self,
|
|
162
|
+
pid: int = None,
|
|
163
|
+
process_name: str = "RobloxPlayerBeta.exe",
|
|
164
|
+
allow_write: bool = False,
|
|
165
|
+
):
|
|
166
|
+
system = platform.system()
|
|
167
|
+
if system not in {"Windows", "Darwin"}:
|
|
168
|
+
self.failed = True
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
if system == "Darwin" and os.geteuid() != 0:
|
|
172
|
+
raise PermissionError(
|
|
173
|
+
"macOS memory access requires running the Python process with sudo/root. "
|
|
174
|
+
"Run this script with sudo and try again."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
from .utils.memory import (
|
|
178
|
+
EvasiveProcess,
|
|
179
|
+
PROCESS_QUERY_INFORMATION,
|
|
180
|
+
PROCESS_VM_READ,
|
|
181
|
+
PROCESS_VM_WRITE,
|
|
182
|
+
PROCESS_VM_OPERATION,
|
|
183
|
+
get_pid_by_name,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if system == "Darwin" and process_name == "RobloxPlayerBeta.exe":
|
|
187
|
+
process_name = "RobloxPlayer"
|
|
188
|
+
|
|
189
|
+
if pid is None:
|
|
190
|
+
self.pid = get_pid_by_name(process_name)
|
|
191
|
+
else:
|
|
192
|
+
self.pid = pid
|
|
193
|
+
|
|
194
|
+
if self.pid is None or self.pid == 0:
|
|
195
|
+
raise ValueError("Failed to get PID.")
|
|
196
|
+
|
|
197
|
+
if system == "Darwin":
|
|
198
|
+
os.environ["ROBLOXMEMORYAPI_ROBLOX_PID"] = str(self.pid)
|
|
199
|
+
|
|
200
|
+
desired_access = PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
|
|
201
|
+
if allow_write:
|
|
202
|
+
desired_access |= PROCESS_VM_WRITE | PROCESS_VM_OPERATION
|
|
203
|
+
|
|
204
|
+
self.memory_module = EvasiveProcess(self.pid, desired_access)
|
|
205
|
+
self.failed = False
|
|
206
|
+
self._fflags = None
|
|
207
|
+
|
|
208
|
+
def close(self):
|
|
209
|
+
self.memory_module.close()
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def FFlags(self):
|
|
213
|
+
if platform.system() not in {"Windows", "Darwin"}:
|
|
214
|
+
raise RuntimeError("This module is only compatible with Windows and macOS.")
|
|
215
|
+
elif self.failed:
|
|
216
|
+
raise RuntimeError("There was an error while getting access to memory. Please try again later.")
|
|
217
|
+
|
|
218
|
+
if self._fflags is None:
|
|
219
|
+
from .utils.rbx.fflags import FFlagManager
|
|
220
|
+
self._fflags = FFlagManager(self.memory_module)
|
|
221
|
+
return self._fflags
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def DataModel(self):
|
|
225
|
+
if platform.system() not in {"Windows", "Darwin"}:
|
|
226
|
+
raise RuntimeError("This module is only compatible with Windows and macOS.")
|
|
227
|
+
elif self.failed:
|
|
228
|
+
raise RuntimeError("There was an error while getting access to memory. Please try again later.")
|
|
229
|
+
|
|
230
|
+
from .utils.rbx.instance import DataModel
|
|
231
|
+
return DataModel(self.memory_module)
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
#include <pybind11/stl.h>
|
|
3
3
|
|
|
4
4
|
#include <algorithm>
|
|
5
|
+
#include <cctype>
|
|
5
6
|
#include <cstdint>
|
|
6
7
|
#include <cstring>
|
|
7
8
|
#include <iomanip>
|
|
9
|
+
#include <optional>
|
|
8
10
|
#include <sstream>
|
|
9
11
|
#include <stdexcept>
|
|
10
12
|
#include <string>
|
|
@@ -26,6 +28,7 @@ namespace py = pybind11;
|
|
|
26
28
|
#include <mach-o/loader.h>
|
|
27
29
|
#include <libproc.h>
|
|
28
30
|
#include <sys/proc_info.h>
|
|
31
|
+
#include <unistd.h>
|
|
29
32
|
#endif
|
|
30
33
|
|
|
31
34
|
namespace {
|
|
@@ -39,6 +42,17 @@ constexpr std::uint32_t MEM_RESERVE_VALUE = 0x2000;
|
|
|
39
42
|
constexpr std::uint32_t PAGE_READWRITE_VALUE = 0x04;
|
|
40
43
|
constexpr std::uint32_t PAGE_EXECUTE_READWRITE_VALUE = 0x40;
|
|
41
44
|
|
|
45
|
+
#if defined(__APPLE__)
|
|
46
|
+
constexpr std::string_view MACOS_CODESIGN_HINT =
|
|
47
|
+
"\n\nTry ad-hoc codesigning Roblox and the Python executable/launcher, "
|
|
48
|
+
"from a sudo/root shell run the following commands:\n"
|
|
49
|
+
"from robloxmemoryapi import codesign_roblox_macos, codesign_python_macos\n\n"
|
|
50
|
+
"# run with sudo\n"
|
|
51
|
+
"codesign_python_macos()\n"
|
|
52
|
+
"codesign_roblox_macos()\n\n"
|
|
53
|
+
"Then restart roblox and run the script again.";
|
|
54
|
+
#endif
|
|
55
|
+
|
|
42
56
|
std::string hex_status(long status) {
|
|
43
57
|
std::ostringstream oss;
|
|
44
58
|
oss << "0x" << std::uppercase << std::hex << static_cast<unsigned long>(status);
|
|
@@ -276,13 +290,22 @@ public:
|
|
|
276
290
|
raise_python(PyExc_ConnectionError, "Failed to get module base address.");
|
|
277
291
|
}
|
|
278
292
|
#elif defined(__APPLE__)
|
|
293
|
+
if (geteuid() != 0) {
|
|
294
|
+
raise_python(
|
|
295
|
+
PyExc_PermissionError,
|
|
296
|
+
"macOS memory access requires running the Python process with sudo/root. "
|
|
297
|
+
"Run this script with sudo and try again."
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
279
301
|
mach_port_t task = MACH_PORT_NULL;
|
|
280
302
|
kern_return_t result = task_for_pid(mach_task_self(), pid, &task);
|
|
281
303
|
if (result != KERN_SUCCESS) {
|
|
282
304
|
raise_python(
|
|
283
305
|
PyExc_OSError,
|
|
284
306
|
"task_for_pid failed: " + std::string(mach_error_string(result)) +
|
|
285
|
-
". macOS memory reads require permission to acquire the target task port."
|
|
307
|
+
". macOS memory reads require permission to acquire the target task port." +
|
|
308
|
+
std::string(MACOS_CODESIGN_HINT)
|
|
286
309
|
);
|
|
287
310
|
}
|
|
288
311
|
|
|
@@ -535,6 +558,104 @@ public:
|
|
|
535
558
|
return py::reinterpret_steal<py::str>(decoded).cast<std::string>();
|
|
536
559
|
}
|
|
537
560
|
|
|
561
|
+
#if defined(__APPLE__)
|
|
562
|
+
static bool is_plausible_string(const std::string &value) {
|
|
563
|
+
if (value.empty()) {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return std::all_of(value.begin(), value.end(), [](const unsigned char c) {
|
|
568
|
+
return c == '\t' || c == '\n' || c == '\r' || std::isprint(c);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
std::optional<std::string> read_libcpp_string(std::uintptr_t address) {
|
|
573
|
+
const std::string bytes = read_bytes(address, 24);
|
|
574
|
+
if (bytes.size() != 24) {
|
|
575
|
+
return std::nullopt;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
auto read_qword = [&](const std::size_t offset) {
|
|
579
|
+
std::uintptr_t value = 0;
|
|
580
|
+
std::memcpy(&value, bytes.data() + offset, sizeof(value));
|
|
581
|
+
return value;
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
auto read_inline = [&](const std::size_t data_offset, const std::size_t size)
|
|
585
|
+
-> std::optional<std::string> {
|
|
586
|
+
if (size == 0 || data_offset + size > bytes.size()) {
|
|
587
|
+
return std::nullopt;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
std::string value(bytes.data() + data_offset, size);
|
|
591
|
+
if (!is_plausible_string(value)) {
|
|
592
|
+
return std::nullopt;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return value;
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// libc++ short layout used by current Roblox macOS builds stores raw size in byte 0.
|
|
599
|
+
if (static_cast<unsigned char>(bytes[0]) <= 23) {
|
|
600
|
+
if (const auto value = read_inline(1, static_cast<unsigned char>(bytes[0]))) {
|
|
601
|
+
return value;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Some libc++ layouts shift the short size left by one.
|
|
606
|
+
if ((static_cast<unsigned char>(bytes[0]) & 1U) == 0 &&
|
|
607
|
+
(static_cast<unsigned char>(bytes[0]) >> 1) <= 23) {
|
|
608
|
+
if (const auto value = read_inline(1, static_cast<unsigned char>(bytes[0]) >> 1)) {
|
|
609
|
+
return value;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Alternate libc++ layout: short data begins at byte 0 and size is stored in byte 23.
|
|
614
|
+
if (static_cast<unsigned char>(bytes[23]) <= 23) {
|
|
615
|
+
if (const auto value = read_inline(0, static_cast<unsigned char>(bytes[23]))) {
|
|
616
|
+
return value;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if ((static_cast<unsigned char>(bytes[23]) & 1U) == 0 &&
|
|
621
|
+
(static_cast<unsigned char>(bytes[23]) >> 1) <= 23) {
|
|
622
|
+
if (const auto value = read_inline(0, static_cast<unsigned char>(bytes[23]) >> 1)) {
|
|
623
|
+
return value;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
auto read_external = [&](std::uintptr_t data_ptr, std::uintptr_t size)
|
|
628
|
+
-> std::optional<std::string> {
|
|
629
|
+
if (data_ptr < 0x10000 || size == 0 || size > 4096) {
|
|
630
|
+
return std::nullopt;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
std::string value = read_raw_string(data_ptr, static_cast<std::size_t>(size + 1));
|
|
634
|
+
if (value.size() != size || !is_plausible_string(value)) {
|
|
635
|
+
return std::nullopt;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return value;
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// libc++ default long layout: cap, size, data.
|
|
642
|
+
if (static_cast<unsigned char>(bytes[0]) & 1U) {
|
|
643
|
+
if (const auto value = read_external(read_qword(16), read_qword(8))) {
|
|
644
|
+
return value;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// libc++ alternate long layout: data, size, cap.
|
|
649
|
+
if (static_cast<unsigned char>(bytes[23]) & 0x80U) {
|
|
650
|
+
if (const auto value = read_external(read_qword(0), read_qword(8))) {
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return std::nullopt;
|
|
656
|
+
}
|
|
657
|
+
#endif
|
|
658
|
+
|
|
538
659
|
void write_raw_string(std::uintptr_t address, const std::string &value, bool null_terminate = true) {
|
|
539
660
|
std::string bytes = value;
|
|
540
661
|
if (null_terminate) {
|
|
@@ -586,6 +707,13 @@ public:
|
|
|
586
707
|
|
|
587
708
|
std::string read_string(std::uintptr_t address, std::uintptr_t offset = 0) {
|
|
588
709
|
address += offset;
|
|
710
|
+
|
|
711
|
+
#if defined(__APPLE__)
|
|
712
|
+
if (const auto value = read_libcpp_string(address)) {
|
|
713
|
+
return *value;
|
|
714
|
+
}
|
|
715
|
+
#endif
|
|
716
|
+
|
|
589
717
|
const int string_length = read_int(address + 0x10);
|
|
590
718
|
|
|
591
719
|
if (string_length <= 0 || string_length > 1024 * 1024) {
|
|
@@ -682,7 +810,11 @@ private:
|
|
|
682
810
|
}
|
|
683
811
|
|
|
684
812
|
if (result != KERN_SUCCESS) {
|
|
685
|
-
raise_python(
|
|
813
|
+
raise_python(
|
|
814
|
+
PyExc_OSError,
|
|
815
|
+
"mach_vm_read_overwrite failed: " + std::string(mach_error_string(result)) +
|
|
816
|
+
std::string(MACOS_CODESIGN_HINT)
|
|
817
|
+
);
|
|
686
818
|
}
|
|
687
819
|
|
|
688
820
|
buffer.resize(static_cast<std::size_t>(bytes_read));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import metadata
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
_DISTRIBUTION_NAME = "robloxmemoryapi"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _read_source_version() -> str | None:
|
|
11
|
+
for parent in Path(__file__).resolve().parents:
|
|
12
|
+
pyproject_path = parent / "pyproject.toml"
|
|
13
|
+
if not pyproject_path.is_file():
|
|
14
|
+
continue
|
|
15
|
+
|
|
16
|
+
in_project_section = False
|
|
17
|
+
for line in pyproject_path.read_text(encoding="utf-8").splitlines():
|
|
18
|
+
stripped = line.strip()
|
|
19
|
+
if stripped == "[project]":
|
|
20
|
+
in_project_section = True
|
|
21
|
+
continue
|
|
22
|
+
if in_project_section and stripped.startswith("["):
|
|
23
|
+
break
|
|
24
|
+
if in_project_section:
|
|
25
|
+
match = re.match(r'version\s*=\s*["\']([^"\']+)["\']', stripped)
|
|
26
|
+
if match:
|
|
27
|
+
return match.group(1)
|
|
28
|
+
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _read_installed_version() -> str | None:
|
|
33
|
+
try:
|
|
34
|
+
return metadata.version(_DISTRIBUTION_NAME)
|
|
35
|
+
except metadata.PackageNotFoundError:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _resolve_version() -> str:
|
|
40
|
+
return _read_source_version() or _read_installed_version() or "0+unknown"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__version__ = _resolve_version()
|
|
44
|
+
USER_AGENT = f"RobloxMemoryAPI/{__version__}"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def normalize_architecture(architecture: str | None) -> str | None:
|
|
10
|
+
if not architecture:
|
|
11
|
+
return None
|
|
12
|
+
|
|
13
|
+
normalized = architecture.strip().lower()
|
|
14
|
+
if normalized in {"arm", "arm64", "aarch64", "apple silicon", "silicon"}:
|
|
15
|
+
return "arm64"
|
|
16
|
+
if normalized in {"intel", "x64", "x86_64", "amd64"}:
|
|
17
|
+
return "x64"
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def process_path(pid: int) -> str | None:
|
|
22
|
+
if platform.system() != "Darwin" or pid <= 0:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
libc = ctypes.CDLL(None)
|
|
27
|
+
proc_pidpath = libc.proc_pidpath
|
|
28
|
+
proc_pidpath.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32]
|
|
29
|
+
proc_pidpath.restype = ctypes.c_int
|
|
30
|
+
|
|
31
|
+
buffer = ctypes.create_string_buffer(4096)
|
|
32
|
+
result = proc_pidpath(pid, buffer, ctypes.sizeof(buffer))
|
|
33
|
+
if result > 0:
|
|
34
|
+
return os.fsdecode(buffer.value)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
output = subprocess.check_output(
|
|
40
|
+
["ps", "-p", str(pid), "-o", "comm="],
|
|
41
|
+
stderr=subprocess.DEVNULL,
|
|
42
|
+
text=True,
|
|
43
|
+
).strip()
|
|
44
|
+
return output or None
|
|
45
|
+
except Exception:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def binary_architecture(path: str | os.PathLike[str] | None) -> str | None:
|
|
50
|
+
if not path:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
path = os.fspath(path)
|
|
54
|
+
if not os.path.exists(path):
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
output = subprocess.check_output(
|
|
59
|
+
["lipo", "-archs", path],
|
|
60
|
+
stderr=subprocess.DEVNULL,
|
|
61
|
+
text=True,
|
|
62
|
+
).strip().lower()
|
|
63
|
+
architectures = set(output.split())
|
|
64
|
+
if architectures == {"arm64"}:
|
|
65
|
+
return "arm64"
|
|
66
|
+
if architectures <= {"x86_64", "i386"} and "x86_64" in architectures:
|
|
67
|
+
return "x64"
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
output = subprocess.check_output(
|
|
73
|
+
["file", "-b", path],
|
|
74
|
+
stderr=subprocess.DEVNULL,
|
|
75
|
+
text=True,
|
|
76
|
+
).lower()
|
|
77
|
+
if "arm64" in output and "x86_64" not in output:
|
|
78
|
+
return "arm64"
|
|
79
|
+
if "x86_64" in output and "arm64" not in output:
|
|
80
|
+
return "x64"
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def running_roblox_binary_path() -> str | None:
|
|
88
|
+
try:
|
|
89
|
+
output = subprocess.check_output(
|
|
90
|
+
[
|
|
91
|
+
"pgrep",
|
|
92
|
+
"-f",
|
|
93
|
+
"/Applications/.*Roblox.*\\.app/Contents/MacOS/RobloxPlayer",
|
|
94
|
+
],
|
|
95
|
+
stderr=subprocess.DEVNULL,
|
|
96
|
+
text=True,
|
|
97
|
+
)
|
|
98
|
+
except Exception:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
for line in output.splitlines():
|
|
102
|
+
try:
|
|
103
|
+
pid = int(line.strip())
|
|
104
|
+
except ValueError:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
path = process_path(pid)
|
|
108
|
+
if path:
|
|
109
|
+
return path
|
|
110
|
+
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def installed_roblox_architecture() -> str | None:
|
|
115
|
+
candidates = [
|
|
116
|
+
"/Applications/Roblox.app/Contents/MacOS/RobloxPlayer",
|
|
117
|
+
"/Applications/RobloxPlayer.app/Contents/MacOS/RobloxPlayer",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
detected = []
|
|
121
|
+
for path in candidates:
|
|
122
|
+
architecture = binary_architecture(path)
|
|
123
|
+
if architecture:
|
|
124
|
+
detected.append(architecture)
|
|
125
|
+
|
|
126
|
+
if len(set(detected)) == 1:
|
|
127
|
+
return detected[0]
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def roblox_architecture() -> str:
|
|
132
|
+
env_architecture = normalize_architecture(os.environ.get("ROBLOXMEMORYAPI_ROBLOX_ARCH"))
|
|
133
|
+
if env_architecture:
|
|
134
|
+
return env_architecture
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
pid = int(os.environ.get("ROBLOXMEMORYAPI_ROBLOX_PID", "0"))
|
|
138
|
+
except ValueError:
|
|
139
|
+
pid = 0
|
|
140
|
+
|
|
141
|
+
architecture = binary_architecture(process_path(pid))
|
|
142
|
+
if architecture:
|
|
143
|
+
return architecture
|
|
144
|
+
|
|
145
|
+
architecture = binary_architecture(running_roblox_binary_path())
|
|
146
|
+
if architecture:
|
|
147
|
+
return architecture
|
|
148
|
+
|
|
149
|
+
architecture = installed_roblox_architecture()
|
|
150
|
+
if architecture:
|
|
151
|
+
return architecture
|
|
152
|
+
|
|
153
|
+
return "arm64" if platform.machine().lower() in {"arm64", "aarch64"} else "x64"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import platform
|
|
3
|
+
|
|
4
|
+
from .._version import USER_AGENT
|
|
5
|
+
from .macos import roblox_architecture
|
|
6
|
+
|
|
7
|
+
IMTHEO_BASE_URL = "https://offsets.imtheo.lol"
|
|
8
|
+
MAC_ARM_OFFSETS_URL = "https://offsets.upio.dev/rbxl-macarm-latest.json"
|
|
9
|
+
MAC_INTEL_OFFSETS_URL = "https://offsets.upio.dev/rbxl-macintel-latest.json"
|
|
10
|
+
REQUEST_HEADERS = {"User-Agent": USER_AGENT}
|
|
11
|
+
|
|
12
|
+
_macos_roblox_architecture = roblox_architecture
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _offsets_url() -> str:
|
|
16
|
+
if platform.system() != "Darwin":
|
|
17
|
+
return f"{IMTHEO_BASE_URL}/Offsets.json"
|
|
18
|
+
|
|
19
|
+
if roblox_architecture() == "arm64":
|
|
20
|
+
return MAC_ARM_OFFSETS_URL
|
|
21
|
+
|
|
22
|
+
return MAC_INTEL_OFFSETS_URL
|
|
23
|
+
|
|
24
|
+
# Offsets
|
|
25
|
+
OffsetsRequest = requests.get(_offsets_url(), headers=REQUEST_HEADERS)
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
Offsets = OffsetsRequest.json()["Offsets"]
|
|
29
|
+
except Exception:
|
|
30
|
+
Offsets = {}
|
|
31
|
+
|
|
32
|
+
# FFlag offsets (lazily loaded)
|
|
33
|
+
_fflag_data = None
|
|
34
|
+
_fflag_offsets = None
|
|
35
|
+
|
|
36
|
+
def get_fflag_offsets() -> dict:
|
|
37
|
+
global _fflag_data, _fflag_offsets
|
|
38
|
+
if _fflag_data is None:
|
|
39
|
+
resp = requests.get(f"{IMTHEO_BASE_URL}/FFlags.json", headers=REQUEST_HEADERS)
|
|
40
|
+
resp.raise_for_status()
|
|
41
|
+
_fflag_data = resp.json()
|
|
42
|
+
_fflag_offsets = _fflag_data["FFlagOffsets"]["FFlags"]
|
|
43
|
+
return _fflag_offsets
|
|
@@ -70,7 +70,7 @@ _ENABLED_OFFSETS_BY_CLASS = {
|
|
|
70
70
|
"BloomEffect": bloom_effect_offsets,
|
|
71
71
|
"DepthOfFieldEffect": depth_of_field_effect_offsets,
|
|
72
72
|
"SunRaysEffect": sun_rays_effect_offsets,
|
|
73
|
-
"ScreenGui": {"Enabled": gui_offsets
|
|
73
|
+
"ScreenGui": {"Enabled": gui_offsets.get("ScreenGui_Enabled")},
|
|
74
74
|
"ProximityPrompt": proximityprompt_offsets,
|
|
75
75
|
"Tool": tool_offsets,
|
|
76
76
|
"SpawnLocation": spawnlocation_offsets,
|
|
@@ -295,6 +295,12 @@ class RBXInstance:
|
|
|
295
295
|
f"{property_name} is only available on {self._format_class_list(offsets_by_class)} instances."
|
|
296
296
|
)
|
|
297
297
|
return None
|
|
298
|
+
if offsets.get(property_name) is None:
|
|
299
|
+
if write:
|
|
300
|
+
raise AttributeError(
|
|
301
|
+
f"{property_name} offset is not available for {self.ClassName} instances."
|
|
302
|
+
)
|
|
303
|
+
return None
|
|
298
304
|
return offsets
|
|
299
305
|
|
|
300
306
|
def _read_class_float(self, property_name, offsets_by_class):
|
|
@@ -4632,8 +4638,13 @@ class DataModel(ServiceBase):
|
|
|
4632
4638
|
|
|
4633
4639
|
with self._refresh_lock:
|
|
4634
4640
|
try:
|
|
4635
|
-
|
|
4636
|
-
|
|
4641
|
+
visual_engine = VisualEngine(self.memory_module)
|
|
4642
|
+
fake_datamodel_ptr = visual_engine.FakeDataModel if not visual_engine.failed else 0
|
|
4643
|
+
datamodel_address_ptr = (
|
|
4644
|
+
self.memory_module.get_pointer(fake_datamodel_ptr, Offsets["FakeDataModel"]["RealDataModel"])
|
|
4645
|
+
if fake_datamodel_ptr != 0
|
|
4646
|
+
else 0
|
|
4647
|
+
)
|
|
4637
4648
|
|
|
4638
4649
|
if datamodel_address_ptr == 0:
|
|
4639
4650
|
if self.instance is not None:
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import platform
|
|
2
|
-
import math
|
|
3
|
-
|
|
4
|
-
__all__ = ["RobloxRandom", "RobloxGameClient", "__version__"]
|
|
5
|
-
__version__ = "0.0.2"
|
|
6
|
-
|
|
7
|
-
class RobloxRandom:
|
|
8
|
-
MULT = 6364136223846793005
|
|
9
|
-
INC = 105
|
|
10
|
-
MASK64 = (1 << 64) - 1
|
|
11
|
-
|
|
12
|
-
def __init__(self, seed):
|
|
13
|
-
s = math.floor(seed)
|
|
14
|
-
|
|
15
|
-
self._state = 0
|
|
16
|
-
self._inc = RobloxRandom.INC
|
|
17
|
-
self._next_internal() # warm-up #1
|
|
18
|
-
self._state = (self._state + s) & RobloxRandom.MASK64
|
|
19
|
-
self._next_internal() # warm-up #2
|
|
20
|
-
|
|
21
|
-
def _next_internal(self):
|
|
22
|
-
old = self._state
|
|
23
|
-
self._state = (old * RobloxRandom.MULT + self._inc) & RobloxRandom.MASK64
|
|
24
|
-
x = ((old >> 18) ^ old) >> 27
|
|
25
|
-
r = old >> 59
|
|
26
|
-
return ((x >> r) | (x << ((32 - r) & 31))) & 0xFFFFFFFF
|
|
27
|
-
|
|
28
|
-
def _next_fraction64(self):
|
|
29
|
-
lo = self._next_internal()
|
|
30
|
-
hi = self._next_internal()
|
|
31
|
-
bits = (hi << 32) | lo
|
|
32
|
-
return bits / 2**64
|
|
33
|
-
|
|
34
|
-
def NextNumber(self, minimum=0.0, maximum=1.0):
|
|
35
|
-
frac = self._next_fraction64()
|
|
36
|
-
return minimum + frac * (maximum - minimum)
|
|
37
|
-
|
|
38
|
-
def NextInteger(self, a, b=None):
|
|
39
|
-
if b is None:
|
|
40
|
-
u = a
|
|
41
|
-
r = self._next_internal()
|
|
42
|
-
return ((u * r) >> 32) + 1
|
|
43
|
-
else:
|
|
44
|
-
lo, hi = (a, b) if a <= b else (b, a)
|
|
45
|
-
u = hi - lo + 1
|
|
46
|
-
r = self._next_internal()
|
|
47
|
-
return ((u * r) >> 32) + lo
|
|
48
|
-
|
|
49
|
-
class RobloxGameClient:
|
|
50
|
-
def __init__(
|
|
51
|
-
self,
|
|
52
|
-
pid: int = None,
|
|
53
|
-
process_name: str = "RobloxPlayerBeta.exe",
|
|
54
|
-
allow_write: bool = False,
|
|
55
|
-
):
|
|
56
|
-
system = platform.system()
|
|
57
|
-
if system not in {"Windows", "Darwin"}:
|
|
58
|
-
self.failed = True
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
from .utils.memory import (
|
|
62
|
-
EvasiveProcess,
|
|
63
|
-
PROCESS_QUERY_INFORMATION,
|
|
64
|
-
PROCESS_VM_READ,
|
|
65
|
-
PROCESS_VM_WRITE,
|
|
66
|
-
PROCESS_VM_OPERATION,
|
|
67
|
-
get_pid_by_name,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
if system == "Darwin" and process_name == "RobloxPlayerBeta.exe":
|
|
71
|
-
process_name = "RobloxPlayer"
|
|
72
|
-
|
|
73
|
-
if pid is None:
|
|
74
|
-
self.pid = get_pid_by_name(process_name)
|
|
75
|
-
else:
|
|
76
|
-
self.pid = pid
|
|
77
|
-
|
|
78
|
-
if self.pid is None or self.pid == 0:
|
|
79
|
-
raise ValueError("Failed to get PID.")
|
|
80
|
-
|
|
81
|
-
desired_access = PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
|
|
82
|
-
if allow_write:
|
|
83
|
-
desired_access |= PROCESS_VM_WRITE | PROCESS_VM_OPERATION
|
|
84
|
-
|
|
85
|
-
self.memory_module = EvasiveProcess(self.pid, desired_access)
|
|
86
|
-
self.failed = False
|
|
87
|
-
self._fflags = None
|
|
88
|
-
|
|
89
|
-
def close(self):
|
|
90
|
-
self.memory_module.close()
|
|
91
|
-
|
|
92
|
-
@property
|
|
93
|
-
def FFlags(self):
|
|
94
|
-
if platform.system() not in {"Windows", "Darwin"}:
|
|
95
|
-
raise RuntimeError("This module is only compatible with Windows and macOS.")
|
|
96
|
-
elif self.failed:
|
|
97
|
-
raise RuntimeError("There was an error while getting access to memory. Please try again later.")
|
|
98
|
-
|
|
99
|
-
if self._fflags is None:
|
|
100
|
-
from .utils.rbx.fflags import FFlagManager
|
|
101
|
-
self._fflags = FFlagManager(self.memory_module)
|
|
102
|
-
return self._fflags
|
|
103
|
-
|
|
104
|
-
@property
|
|
105
|
-
def DataModel(self):
|
|
106
|
-
if platform.system() not in {"Windows", "Darwin"}:
|
|
107
|
-
raise RuntimeError("This module is only compatible with Windows and macOS.")
|
|
108
|
-
elif self.failed:
|
|
109
|
-
raise RuntimeError("There was an error while getting access to memory. Please try again later.")
|
|
110
|
-
|
|
111
|
-
from .utils.rbx.instance import DataModel
|
|
112
|
-
return DataModel(self.memory_module)
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
|
|
3
|
-
BASE_URL = "https://offsets.imtheo.lol"
|
|
4
|
-
|
|
5
|
-
# Offsets
|
|
6
|
-
OffsetsRequest = requests.get(f"{BASE_URL}/Offsets.json")
|
|
7
|
-
|
|
8
|
-
try:
|
|
9
|
-
Offsets = OffsetsRequest.json()["Offsets"]
|
|
10
|
-
except:
|
|
11
|
-
Offsets = {}
|
|
12
|
-
|
|
13
|
-
# FFlag offsets (lazily loaded)
|
|
14
|
-
_fflag_data = None
|
|
15
|
-
_fflag_offsets = None
|
|
16
|
-
|
|
17
|
-
def get_fflag_offsets() -> dict:
|
|
18
|
-
global _fflag_data, _fflag_offsets
|
|
19
|
-
if _fflag_data is None:
|
|
20
|
-
resp = requests.get(f"{BASE_URL}/FFlags.json")
|
|
21
|
-
resp.raise_for_status()
|
|
22
|
-
_fflag_data = resp.json()
|
|
23
|
-
_fflag_offsets = _fflag_data["FFlagOffsets"]["FFlags"]
|
|
24
|
-
return _fflag_offsets
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/bytecode/decryptor.py
RENAMED
|
File without changes
|
{robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/bytecode/encryptor.py
RENAMED
|
File without changes
|
{robloxmemoryapi-0.3.1 → robloxmemoryapi-0.3.2}/src/robloxmemoryapi/utils/rbx/datastructures.py
RENAMED
|
File without changes
|
|
File without changes
|