nixwrap-rl 0.1.0__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.
- nixwrap_rl-0.1.0/LICENSE +21 -0
- nixwrap_rl-0.1.0/PKG-INFO +277 -0
- nixwrap_rl-0.1.0/README.md +246 -0
- nixwrap_rl-0.1.0/pyproject.toml +52 -0
- nixwrap_rl-0.1.0/setup.cfg +4 -0
- nixwrap_rl-0.1.0/src/nixwrap/__init__.py +3 -0
- nixwrap_rl-0.1.0/src/nixwrap/_version.py +3 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/__init__.py +77 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_animations.py +257 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_app.py +152 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_colors.py +151 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_geometry.py +171 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_images.py +94 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_painter.py +204 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_text.py +76 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_themes.py +40 -0
- nixwrap_rl-0.1.0/src/nixwrap/gui/_window.py +308 -0
- nixwrap_rl-0.1.0/src/nixwrap/input/__init__.py +51 -0
- nixwrap_rl-0.1.0/src/nixwrap/input/_bindings.py +88 -0
- nixwrap_rl-0.1.0/src/nixwrap/input/_dualsense.py +220 -0
- nixwrap_rl-0.1.0/src/nixwrap/input/_keyboard.py +72 -0
- nixwrap_rl-0.1.0/src/nixwrap/input/_xinput.py +116 -0
- nixwrap_rl-0.1.0/src/nixwrap/input/hidapi.dll +0 -0
- nixwrap_rl-0.1.0/src/nixwrap/process/__init__.py +25 -0
- nixwrap_rl-0.1.0/src/nixwrap/process/_detector.py +150 -0
- nixwrap_rl-0.1.0/src/nixwrap/process/_window.py +89 -0
- nixwrap_rl-0.1.0/src/nixwrap/py.typed +0 -0
- nixwrap_rl-0.1.0/src/nixwrap/save_file/__init__.py +207 -0
- nixwrap_rl-0.1.0/src/nixwrap/save_file/_accessors.py +767 -0
- nixwrap_rl-0.1.0/src/nixwrap/save_file/_binary_parser.py +228 -0
- nixwrap_rl-0.1.0/src/nixwrap/save_file/_binary_serializer.py +107 -0
- nixwrap_rl-0.1.0/src/nixwrap/save_file/_cli.py +42 -0
- nixwrap_rl-0.1.0/src/nixwrap/save_file/_crypto.py +66 -0
- nixwrap_rl-0.1.0/src/nixwrap/save_file/_file_io.py +192 -0
- nixwrap_rl-0.1.0/src/nixwrap/save_file/_models.py +321 -0
- nixwrap_rl-0.1.0/src/nixwrap/stats/__init__.py +25 -0
- nixwrap_rl-0.1.0/src/nixwrap/stats/_client.py +238 -0
- nixwrap_rl-0.1.0/src/nixwrap/stats/_models.py +169 -0
- nixwrap_rl-0.1.0/src/nixwrap/stats/_parser.py +290 -0
- nixwrap_rl-0.1.0/src/nixwrap/tracker/__init__.py +20 -0
- nixwrap_rl-0.1.0/src/nixwrap/tracker/_cache.py +80 -0
- nixwrap_rl-0.1.0/src/nixwrap/tracker/_client.py +370 -0
- nixwrap_rl-0.1.0/src/nixwrap/tracker/_models.py +78 -0
- nixwrap_rl-0.1.0/src/nixwrap/utils/__init__.py +56 -0
- nixwrap_rl-0.1.0/src/nixwrap/utils/_constants.py +80 -0
- nixwrap_rl-0.1.0/src/nixwrap/utils/_platforms.py +66 -0
- nixwrap_rl-0.1.0/src/nixwrap/utils/_ranks.py +88 -0
- nixwrap_rl-0.1.0/src/nixwrap_rl.egg-info/PKG-INFO +277 -0
- nixwrap_rl-0.1.0/src/nixwrap_rl.egg-info/SOURCES.txt +50 -0
- nixwrap_rl-0.1.0/src/nixwrap_rl.egg-info/dependency_links.txt +1 -0
- nixwrap_rl-0.1.0/src/nixwrap_rl.egg-info/requires.txt +15 -0
- nixwrap_rl-0.1.0/src/nixwrap_rl.egg-info/top_level.txt +1 -0
nixwrap_rl-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nixvio64
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nixwrap-rl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unified SDK for Rocket League save file data extraction, Stats API WebSocket client, tracker.gg stats, process detection, input (keyboard/controller), and GUI overlay toolkit.
|
|
5
|
+
Author: nixvio64
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nixvio64/nixwrap
|
|
8
|
+
Project-URL: Source, https://github.com/nixvio64/nixwrap
|
|
9
|
+
Project-URL: Issues, https://github.com/nixvio64/nixwrap/issues
|
|
10
|
+
Keywords: rocket-league,save-file,stats-api,overlay,tracker,bakkesmod
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Topic :: Games/Entertainment
|
|
16
|
+
Requires-Python: ==3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: pycryptodome>=3.18
|
|
20
|
+
Requires-Dist: psutil>=5.9
|
|
21
|
+
Provides-Extra: tracker
|
|
22
|
+
Requires-Dist: curl-cffi>=0.6; extra == "tracker"
|
|
23
|
+
Provides-Extra: gui
|
|
24
|
+
Requires-Dist: PySide6>=6.5; extra == "gui"
|
|
25
|
+
Provides-Extra: input
|
|
26
|
+
Requires-Dist: dualsense-controller>=0.3; extra == "input"
|
|
27
|
+
Requires-Dist: keyboard>=0.13; extra == "input"
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: nixwrap[gui,input,tracker]; extra == "all"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Nixwrap
|
|
33
|
+
|
|
34
|
+
Unified Python SDK for Rocket League: save file data extraction, Stats API WebSocket client, tracker.gg stats, process detection, keyboard/controller input, and a reusable GUI/overlay toolkit.
|
|
35
|
+
|
|
36
|
+
Built because EAC made traditional plugin development for online matches harder. Intended for streamers, tool developers, and anyone who wants RL data in Python.
|
|
37
|
+
```bash
|
|
38
|
+
pip install git+https://github.com/nixvio64/nixwrap
|
|
39
|
+
|
|
40
|
+
# With optional extras:
|
|
41
|
+
pip install "nixwrap[tracker] @ git+https://github.com/nixvio64/nixwrap" # tracker.gg
|
|
42
|
+
pip install "nixwrap[gui] @ git+https://github.com/nixvio64/nixwrap" # overlay / GUI
|
|
43
|
+
pip install "nixwrap[input] @ git+https://github.com/nixvio64/nixwrap" # keyboard + controller
|
|
44
|
+
pip install "nixwrap[all] @ git+https://github.com/nixvio64/nixwrap" # everything
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Modules
|
|
49
|
+
|
|
50
|
+
| Module | Purpose | Requires |
|
|
51
|
+
|--------|---------|----------|
|
|
52
|
+
| `nixwrap.save_file` | Decrypt + extract data from `.save` files | `pycryptodome` |
|
|
53
|
+
| `nixwrap.process` | Detect running RL (Steam vs Epic) | `psutil` |
|
|
54
|
+
| `nixwrap.stats` | Live Stats API WebSocket events | - |
|
|
55
|
+
| `nixwrap.tracker` | tracker.gg player rank/stats | `curl-cffi` (optional) |
|
|
56
|
+
| `nixwrap.input` | Keyboard, Xbox, DualSense hotkey detection | `dualsense-controller`, `keyboard` (optional) |
|
|
57
|
+
| `nixwrap.gui` | Overlay/GUI toolkit | `PySide6` (optional) |
|
|
58
|
+
| `nixwrap.utils` | Constants and helpers | - |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
### 0. Edit your DefaultStatsAPI.ini
|
|
65
|
+
|
|
66
|
+
Found at ```<RL Install Dir>\TAGame\Config\DefaultStatsAPI.ini``` set PacketSendRate value to something like 20 (1-120).
|
|
67
|
+
|
|
68
|
+
### 1. Save File Data Extraction
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from nixwrap.save_file import load, find_save_file
|
|
72
|
+
|
|
73
|
+
# Auto-find the active save file
|
|
74
|
+
path = find_save_file()
|
|
75
|
+
save = load(path)
|
|
76
|
+
|
|
77
|
+
# Camera
|
|
78
|
+
print(save.camera.fov) # 110.0
|
|
79
|
+
print(save.camera.stiffness) # 0.55
|
|
80
|
+
|
|
81
|
+
# Controls (keyboard + gamepad)
|
|
82
|
+
print(save.controls.jump) # "SpaceBar"
|
|
83
|
+
print(save.gamepad_bindings.boost) # "XboxTypeS_RightShoulder"
|
|
84
|
+
|
|
85
|
+
# Raw bindings for custom inspection
|
|
86
|
+
for b in save.gamepad_bindings.raw_bindings:
|
|
87
|
+
print(f" {b['Action']} -> {b['Key']}")
|
|
88
|
+
|
|
89
|
+
# Stats
|
|
90
|
+
print(save.stats.wins) # 1234
|
|
91
|
+
print(save.xp.level) # 27
|
|
92
|
+
print(save.xp.total_xp) # 348223
|
|
93
|
+
|
|
94
|
+
# Skill data per playlist
|
|
95
|
+
for pid, skill in save.skills.items():
|
|
96
|
+
print(f"Playlist {pid}: matches={skill.matches_played}, tier={skill.tier}")
|
|
97
|
+
|
|
98
|
+
# Inventory
|
|
99
|
+
for item in save.inventory:
|
|
100
|
+
print(f"Product {item.product_id} (series {item.series_id})")
|
|
101
|
+
|
|
102
|
+
# Settings
|
|
103
|
+
print(save.video.res_width, save.video.res_height)
|
|
104
|
+
print(save.gameplay.controller_deadzone)
|
|
105
|
+
print(save.sound.master_volume)
|
|
106
|
+
|
|
107
|
+
# Quick chats, loadout sets, season, achievements, etc.
|
|
108
|
+
print(len(save.quick_chats), "quick chat bindings")
|
|
109
|
+
print(len(save.loadout_sets), "loadout presets")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 2. Process Detection
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from nixwrap.process import find_rocket_league, is_rocket_league_focused
|
|
116
|
+
|
|
117
|
+
rl = find_rocket_league()
|
|
118
|
+
if rl:
|
|
119
|
+
print(f"PID: {rl.pid}")
|
|
120
|
+
print(f"Platform: {rl.platform.value}") # steam or epic
|
|
121
|
+
print(f"Root dir: {rl.root_dir}")
|
|
122
|
+
print(f"Save path: {rl.save_data_path}")
|
|
123
|
+
|
|
124
|
+
if is_rocket_league_focused():
|
|
125
|
+
print("RL window has focus")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 3. Input Detection (Keyboard + Controller)
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from nixwrap.input import (
|
|
132
|
+
is_hotkey_pressed, is_key_pressed,
|
|
133
|
+
get_xinput_state, XINPUT_A,
|
|
134
|
+
setup_dualsense, start_dualsense_monitor,
|
|
135
|
+
get_button_display,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Keyboard
|
|
139
|
+
if is_key_pressed("tab"):
|
|
140
|
+
print("Tab is held")
|
|
141
|
+
|
|
142
|
+
# XInput (Xbox / DS4 via DS4Windows)
|
|
143
|
+
state = get_xinput_state()
|
|
144
|
+
if state and state.is_pressed(XINPUT_A):
|
|
145
|
+
print("A button pressed")
|
|
146
|
+
|
|
147
|
+
# DualSense (PS5)
|
|
148
|
+
controller = setup_dualsense() # auto-finds bundled hidapi.dll
|
|
149
|
+
start_dualsense_monitor() # background auto-reconnect
|
|
150
|
+
|
|
151
|
+
# Unified hotkey check (InGameRank-compatible config format)
|
|
152
|
+
config = {"is_controller": True, "controller_type": "xinput",
|
|
153
|
+
"controller_button": XINPUT_A}
|
|
154
|
+
if is_hotkey_pressed(config):
|
|
155
|
+
print("Hotkey held!")
|
|
156
|
+
|
|
157
|
+
# Button display names
|
|
158
|
+
print(get_button_display("xinput", XINPUT_A)) # "A"
|
|
159
|
+
print(get_button_display("dualsense", 0)) # "Cross"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 4. Stats API (Live Events)
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from nixwrap.stats import StatsClient
|
|
166
|
+
|
|
167
|
+
client = StatsClient()
|
|
168
|
+
|
|
169
|
+
# Callback mode
|
|
170
|
+
client.on("GoalScored", lambda evt: print(f"GOAL! {evt.scorer.name}"))
|
|
171
|
+
client.on("StatfeedEvent", lambda evt: print(f"{evt.main_target.name}: {evt.stat_type}"))
|
|
172
|
+
client.on_event(lambda evt: print(f"[{evt.event_type}]"))
|
|
173
|
+
|
|
174
|
+
with client:
|
|
175
|
+
import time
|
|
176
|
+
time.sleep(300) # listen for 5 minutes
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Async mode:**
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
import asyncio
|
|
183
|
+
from nixwrap.stats import StatsClient
|
|
184
|
+
|
|
185
|
+
async def main():
|
|
186
|
+
client = StatsClient()
|
|
187
|
+
client.start()
|
|
188
|
+
async for event in client.events():
|
|
189
|
+
print(event.event_type)
|
|
190
|
+
if event.event_type == "GoalScored":
|
|
191
|
+
print(f" {event.scorer.name} scored!")
|
|
192
|
+
|
|
193
|
+
asyncio.run(main())
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 5. Tracker API (Player Ranks)
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from nixwrap.tracker import TrackerClient
|
|
200
|
+
|
|
201
|
+
client = TrackerClient() # random browser fingerprint per request
|
|
202
|
+
stats = client.fetch("Steam|123456789|0", "PlayerName")
|
|
203
|
+
|
|
204
|
+
if stats.error:
|
|
205
|
+
print(f"Error: {stats.error}")
|
|
206
|
+
else:
|
|
207
|
+
print(f"Last updated: {stats.last_updated}")
|
|
208
|
+
print(f"Lifetime: {stats.lifetime.wins} wins, {stats.lifetime.goals} goals")
|
|
209
|
+
for pid, rank in stats.ranks.items():
|
|
210
|
+
print(f" {rank.playlist_name}: {rank.tier_name} {rank.division_name} "
|
|
211
|
+
f"({rank.mmr} MMR, #{rank.rank_percentile}%)")
|
|
212
|
+
if rank.win_streak:
|
|
213
|
+
direction = "^" if rank.win_streak_type == "win" else "v"
|
|
214
|
+
print(f" Streak: {direction}{rank.win_streak}")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Results are cached for 5 minutes (tracker.gg update interval).
|
|
218
|
+
|
|
219
|
+
### 6. GUI / Overlay
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from PySide6.QtWidgets import QApplication
|
|
223
|
+
from nixwrap.gui import (
|
|
224
|
+
create_overlay, WindowConfig, WindowType,
|
|
225
|
+
Painter, Color, THEME_DARK, FadeAnimation,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
class MyOverlay:
|
|
229
|
+
def __init__(self):
|
|
230
|
+
self.win = create_overlay(600, 200, corner_radius=12, opacity=0.95)
|
|
231
|
+
self.win._paint = self._paint
|
|
232
|
+
self.win.snap_to_bottom_center(margin=20)
|
|
233
|
+
|
|
234
|
+
def _paint(self, painter_q):
|
|
235
|
+
p = Painter(painter_q)
|
|
236
|
+
p.fill_rect(0, 0, 600, 200,
|
|
237
|
+
THEME_DARK.background.with_alpha(216), radius=12)
|
|
238
|
+
p.stroke_rect(0, 0, 599, 199,
|
|
239
|
+
THEME_DARK.divider, width=1, radius=12)
|
|
240
|
+
p.draw_text("Hello Rocket League!", 20, 40,
|
|
241
|
+
color=THEME_DARK.text_primary)
|
|
242
|
+
|
|
243
|
+
app = QApplication([])
|
|
244
|
+
overlay = MyOverlay()
|
|
245
|
+
app.exec()
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Optional Dependencies
|
|
251
|
+
|
|
252
|
+
| Extra | Packages | What you get |
|
|
253
|
+
|-------|----------|-------------|
|
|
254
|
+
| `[tracker]` | `curl-cffi` | better tracker.gg support |
|
|
255
|
+
| `[gui]` | `PySide6` | Overlay windows, painter, colors, fonts, animations |
|
|
256
|
+
| `[input]` | `dualsense-controller`, `keyboard`, bundled `hidapi.dll` | Xbox (XInput), PS5 DualSense, keyboard hotkey detection |
|
|
257
|
+
| `[all]` | all of the above | Everything |
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Requirements
|
|
262
|
+
|
|
263
|
+
- **Python** >= 3.10
|
|
264
|
+
- **pycryptodome** >= 3.18 (save file AES)
|
|
265
|
+
- **psutil** >= 5.9 (process detection)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Acknowledgements
|
|
271
|
+
|
|
272
|
+
- [RocketRP](https://github.com/Drogebot/RocketRP): AES key and binary format research
|
|
273
|
+
- [tracker.gg](https://tracker.gg/): player stats API
|
|
274
|
+
- Rocket League Stats API: [Psyonix/Epic](https://www.rocketleague.com/en/developer/stats-api)
|
|
275
|
+
|
|
276
|
+
## Contributing
|
|
277
|
+
Do you want to make this library even better? Contributions are highly encouraged and appreciated.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Nixwrap
|
|
2
|
+
|
|
3
|
+
Unified Python SDK for Rocket League: save file data extraction, Stats API WebSocket client, tracker.gg stats, process detection, keyboard/controller input, and a reusable GUI/overlay toolkit.
|
|
4
|
+
|
|
5
|
+
Built because EAC made traditional plugin development for online matches harder. Intended for streamers, tool developers, and anyone who wants RL data in Python.
|
|
6
|
+
```bash
|
|
7
|
+
pip install git+https://github.com/nixvio64/nixwrap
|
|
8
|
+
|
|
9
|
+
# With optional extras:
|
|
10
|
+
pip install "nixwrap[tracker] @ git+https://github.com/nixvio64/nixwrap" # tracker.gg
|
|
11
|
+
pip install "nixwrap[gui] @ git+https://github.com/nixvio64/nixwrap" # overlay / GUI
|
|
12
|
+
pip install "nixwrap[input] @ git+https://github.com/nixvio64/nixwrap" # keyboard + controller
|
|
13
|
+
pip install "nixwrap[all] @ git+https://github.com/nixvio64/nixwrap" # everything
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Modules
|
|
18
|
+
|
|
19
|
+
| Module | Purpose | Requires |
|
|
20
|
+
|--------|---------|----------|
|
|
21
|
+
| `nixwrap.save_file` | Decrypt + extract data from `.save` files | `pycryptodome` |
|
|
22
|
+
| `nixwrap.process` | Detect running RL (Steam vs Epic) | `psutil` |
|
|
23
|
+
| `nixwrap.stats` | Live Stats API WebSocket events | - |
|
|
24
|
+
| `nixwrap.tracker` | tracker.gg player rank/stats | `curl-cffi` (optional) |
|
|
25
|
+
| `nixwrap.input` | Keyboard, Xbox, DualSense hotkey detection | `dualsense-controller`, `keyboard` (optional) |
|
|
26
|
+
| `nixwrap.gui` | Overlay/GUI toolkit | `PySide6` (optional) |
|
|
27
|
+
| `nixwrap.utils` | Constants and helpers | - |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### 0. Edit your DefaultStatsAPI.ini
|
|
34
|
+
|
|
35
|
+
Found at ```<RL Install Dir>\TAGame\Config\DefaultStatsAPI.ini``` set PacketSendRate value to something like 20 (1-120).
|
|
36
|
+
|
|
37
|
+
### 1. Save File Data Extraction
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from nixwrap.save_file import load, find_save_file
|
|
41
|
+
|
|
42
|
+
# Auto-find the active save file
|
|
43
|
+
path = find_save_file()
|
|
44
|
+
save = load(path)
|
|
45
|
+
|
|
46
|
+
# Camera
|
|
47
|
+
print(save.camera.fov) # 110.0
|
|
48
|
+
print(save.camera.stiffness) # 0.55
|
|
49
|
+
|
|
50
|
+
# Controls (keyboard + gamepad)
|
|
51
|
+
print(save.controls.jump) # "SpaceBar"
|
|
52
|
+
print(save.gamepad_bindings.boost) # "XboxTypeS_RightShoulder"
|
|
53
|
+
|
|
54
|
+
# Raw bindings for custom inspection
|
|
55
|
+
for b in save.gamepad_bindings.raw_bindings:
|
|
56
|
+
print(f" {b['Action']} -> {b['Key']}")
|
|
57
|
+
|
|
58
|
+
# Stats
|
|
59
|
+
print(save.stats.wins) # 1234
|
|
60
|
+
print(save.xp.level) # 27
|
|
61
|
+
print(save.xp.total_xp) # 348223
|
|
62
|
+
|
|
63
|
+
# Skill data per playlist
|
|
64
|
+
for pid, skill in save.skills.items():
|
|
65
|
+
print(f"Playlist {pid}: matches={skill.matches_played}, tier={skill.tier}")
|
|
66
|
+
|
|
67
|
+
# Inventory
|
|
68
|
+
for item in save.inventory:
|
|
69
|
+
print(f"Product {item.product_id} (series {item.series_id})")
|
|
70
|
+
|
|
71
|
+
# Settings
|
|
72
|
+
print(save.video.res_width, save.video.res_height)
|
|
73
|
+
print(save.gameplay.controller_deadzone)
|
|
74
|
+
print(save.sound.master_volume)
|
|
75
|
+
|
|
76
|
+
# Quick chats, loadout sets, season, achievements, etc.
|
|
77
|
+
print(len(save.quick_chats), "quick chat bindings")
|
|
78
|
+
print(len(save.loadout_sets), "loadout presets")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 2. Process Detection
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from nixwrap.process import find_rocket_league, is_rocket_league_focused
|
|
85
|
+
|
|
86
|
+
rl = find_rocket_league()
|
|
87
|
+
if rl:
|
|
88
|
+
print(f"PID: {rl.pid}")
|
|
89
|
+
print(f"Platform: {rl.platform.value}") # steam or epic
|
|
90
|
+
print(f"Root dir: {rl.root_dir}")
|
|
91
|
+
print(f"Save path: {rl.save_data_path}")
|
|
92
|
+
|
|
93
|
+
if is_rocket_league_focused():
|
|
94
|
+
print("RL window has focus")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 3. Input Detection (Keyboard + Controller)
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from nixwrap.input import (
|
|
101
|
+
is_hotkey_pressed, is_key_pressed,
|
|
102
|
+
get_xinput_state, XINPUT_A,
|
|
103
|
+
setup_dualsense, start_dualsense_monitor,
|
|
104
|
+
get_button_display,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Keyboard
|
|
108
|
+
if is_key_pressed("tab"):
|
|
109
|
+
print("Tab is held")
|
|
110
|
+
|
|
111
|
+
# XInput (Xbox / DS4 via DS4Windows)
|
|
112
|
+
state = get_xinput_state()
|
|
113
|
+
if state and state.is_pressed(XINPUT_A):
|
|
114
|
+
print("A button pressed")
|
|
115
|
+
|
|
116
|
+
# DualSense (PS5)
|
|
117
|
+
controller = setup_dualsense() # auto-finds bundled hidapi.dll
|
|
118
|
+
start_dualsense_monitor() # background auto-reconnect
|
|
119
|
+
|
|
120
|
+
# Unified hotkey check (InGameRank-compatible config format)
|
|
121
|
+
config = {"is_controller": True, "controller_type": "xinput",
|
|
122
|
+
"controller_button": XINPUT_A}
|
|
123
|
+
if is_hotkey_pressed(config):
|
|
124
|
+
print("Hotkey held!")
|
|
125
|
+
|
|
126
|
+
# Button display names
|
|
127
|
+
print(get_button_display("xinput", XINPUT_A)) # "A"
|
|
128
|
+
print(get_button_display("dualsense", 0)) # "Cross"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 4. Stats API (Live Events)
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from nixwrap.stats import StatsClient
|
|
135
|
+
|
|
136
|
+
client = StatsClient()
|
|
137
|
+
|
|
138
|
+
# Callback mode
|
|
139
|
+
client.on("GoalScored", lambda evt: print(f"GOAL! {evt.scorer.name}"))
|
|
140
|
+
client.on("StatfeedEvent", lambda evt: print(f"{evt.main_target.name}: {evt.stat_type}"))
|
|
141
|
+
client.on_event(lambda evt: print(f"[{evt.event_type}]"))
|
|
142
|
+
|
|
143
|
+
with client:
|
|
144
|
+
import time
|
|
145
|
+
time.sleep(300) # listen for 5 minutes
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Async mode:**
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
import asyncio
|
|
152
|
+
from nixwrap.stats import StatsClient
|
|
153
|
+
|
|
154
|
+
async def main():
|
|
155
|
+
client = StatsClient()
|
|
156
|
+
client.start()
|
|
157
|
+
async for event in client.events():
|
|
158
|
+
print(event.event_type)
|
|
159
|
+
if event.event_type == "GoalScored":
|
|
160
|
+
print(f" {event.scorer.name} scored!")
|
|
161
|
+
|
|
162
|
+
asyncio.run(main())
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 5. Tracker API (Player Ranks)
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from nixwrap.tracker import TrackerClient
|
|
169
|
+
|
|
170
|
+
client = TrackerClient() # random browser fingerprint per request
|
|
171
|
+
stats = client.fetch("Steam|123456789|0", "PlayerName")
|
|
172
|
+
|
|
173
|
+
if stats.error:
|
|
174
|
+
print(f"Error: {stats.error}")
|
|
175
|
+
else:
|
|
176
|
+
print(f"Last updated: {stats.last_updated}")
|
|
177
|
+
print(f"Lifetime: {stats.lifetime.wins} wins, {stats.lifetime.goals} goals")
|
|
178
|
+
for pid, rank in stats.ranks.items():
|
|
179
|
+
print(f" {rank.playlist_name}: {rank.tier_name} {rank.division_name} "
|
|
180
|
+
f"({rank.mmr} MMR, #{rank.rank_percentile}%)")
|
|
181
|
+
if rank.win_streak:
|
|
182
|
+
direction = "^" if rank.win_streak_type == "win" else "v"
|
|
183
|
+
print(f" Streak: {direction}{rank.win_streak}")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Results are cached for 5 minutes (tracker.gg update interval).
|
|
187
|
+
|
|
188
|
+
### 6. GUI / Overlay
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from PySide6.QtWidgets import QApplication
|
|
192
|
+
from nixwrap.gui import (
|
|
193
|
+
create_overlay, WindowConfig, WindowType,
|
|
194
|
+
Painter, Color, THEME_DARK, FadeAnimation,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
class MyOverlay:
|
|
198
|
+
def __init__(self):
|
|
199
|
+
self.win = create_overlay(600, 200, corner_radius=12, opacity=0.95)
|
|
200
|
+
self.win._paint = self._paint
|
|
201
|
+
self.win.snap_to_bottom_center(margin=20)
|
|
202
|
+
|
|
203
|
+
def _paint(self, painter_q):
|
|
204
|
+
p = Painter(painter_q)
|
|
205
|
+
p.fill_rect(0, 0, 600, 200,
|
|
206
|
+
THEME_DARK.background.with_alpha(216), radius=12)
|
|
207
|
+
p.stroke_rect(0, 0, 599, 199,
|
|
208
|
+
THEME_DARK.divider, width=1, radius=12)
|
|
209
|
+
p.draw_text("Hello Rocket League!", 20, 40,
|
|
210
|
+
color=THEME_DARK.text_primary)
|
|
211
|
+
|
|
212
|
+
app = QApplication([])
|
|
213
|
+
overlay = MyOverlay()
|
|
214
|
+
app.exec()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Optional Dependencies
|
|
220
|
+
|
|
221
|
+
| Extra | Packages | What you get |
|
|
222
|
+
|-------|----------|-------------|
|
|
223
|
+
| `[tracker]` | `curl-cffi` | better tracker.gg support |
|
|
224
|
+
| `[gui]` | `PySide6` | Overlay windows, painter, colors, fonts, animations |
|
|
225
|
+
| `[input]` | `dualsense-controller`, `keyboard`, bundled `hidapi.dll` | Xbox (XInput), PS5 DualSense, keyboard hotkey detection |
|
|
226
|
+
| `[all]` | all of the above | Everything |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Requirements
|
|
231
|
+
|
|
232
|
+
- **Python** >= 3.10
|
|
233
|
+
- **pycryptodome** >= 3.18 (save file AES)
|
|
234
|
+
- **psutil** >= 5.9 (process detection)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Acknowledgements
|
|
240
|
+
|
|
241
|
+
- [RocketRP](https://github.com/Drogebot/RocketRP): AES key and binary format research
|
|
242
|
+
- [tracker.gg](https://tracker.gg/): player stats API
|
|
243
|
+
- Rocket League Stats API: [Psyonix/Epic](https://www.rocketleague.com/en/developer/stats-api)
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
Do you want to make this library even better? Contributions are highly encouraged and appreciated.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nixwrap-rl"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unified SDK for Rocket League save file data extraction, Stats API WebSocket client, tracker.gg stats, process detection, input (keyboard/controller), and GUI overlay toolkit."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = "==3.11"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "nixvio64"}]
|
|
13
|
+
keywords = ["rocket-league", "save-file", "stats-api", "overlay", "tracker", "bakkesmod"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Topic :: Games/Entertainment",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pycryptodome>=3.18",
|
|
24
|
+
"psutil>=5.9",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/nixvio64/nixwrap"
|
|
29
|
+
Source = "https://github.com/nixvio64/nixwrap"
|
|
30
|
+
Issues = "https://github.com/nixvio64/nixwrap/issues"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
tracker = [
|
|
34
|
+
"curl-cffi>=0.6",
|
|
35
|
+
]
|
|
36
|
+
gui = [
|
|
37
|
+
"PySide6>=6.5",
|
|
38
|
+
]
|
|
39
|
+
input = [
|
|
40
|
+
"dualsense-controller>=0.3",
|
|
41
|
+
"keyboard>=0.13",
|
|
42
|
+
]
|
|
43
|
+
all = [
|
|
44
|
+
"nixwrap[tracker,gui,input]",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["src"]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.package-data]
|
|
51
|
+
nixwrap = ["py.typed"]
|
|
52
|
+
"nixwrap.input" = ["hidapi.dll"]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Overlay/GUI toolkit. Needs PySide6 (pip install nixwrap[gui])."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def __getattr__(name: str):
|
|
5
|
+
"""Lazy-load all public names, checking for PySide6."""
|
|
6
|
+
_ensure_pyside6()
|
|
7
|
+
# Import the real module
|
|
8
|
+
if name in _EXPORTS:
|
|
9
|
+
mod_name, attr = _EXPORTS[name]
|
|
10
|
+
mod = __import__(f"nixwrap.gui.{mod_name}", fromlist=[attr])
|
|
11
|
+
return getattr(mod, attr)
|
|
12
|
+
raise AttributeError(f"module 'nixwrap.gui' has no attribute {name!r}")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _ensure_pyside6() -> None:
|
|
16
|
+
try:
|
|
17
|
+
import PySide6 # noqa: F401
|
|
18
|
+
except ImportError:
|
|
19
|
+
raise ImportError(
|
|
20
|
+
"The nixwrap.gui module requires PySide6.\n"
|
|
21
|
+
"Install with: pip install nixwrap[gui]\n"
|
|
22
|
+
"Or directly: pip install PySide6"
|
|
23
|
+
) from None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Map public names to (module, attr)
|
|
27
|
+
_EXPORTS: dict[str, tuple[str, str]] = {
|
|
28
|
+
# _window
|
|
29
|
+
"BaseWindow": ("_window", "BaseWindow"),
|
|
30
|
+
"WindowConfig": ("_window", "WindowConfig"),
|
|
31
|
+
"WindowType": ("_window", "WindowType"),
|
|
32
|
+
"create_overlay": ("_window", "create_overlay"),
|
|
33
|
+
"create_frameless_window": ("_window", "create_frameless_window"),
|
|
34
|
+
"create_window": ("_window", "create_window"),
|
|
35
|
+
# _painter
|
|
36
|
+
"Painter": ("_painter", "Painter"),
|
|
37
|
+
# _colors
|
|
38
|
+
"Color": ("_colors", "Color"),
|
|
39
|
+
"Gradient": ("_colors", "Gradient"),
|
|
40
|
+
"TEAM_BLUE": ("_colors", "TEAM_BLUE"),
|
|
41
|
+
"TEAM_ORANGE": ("_colors", "TEAM_ORANGE"),
|
|
42
|
+
"BACKGROUND_DARK": ("_colors", "BACKGROUND_DARK"),
|
|
43
|
+
"DIVIDER_GRAY": ("_colors", "DIVIDER_GRAY"),
|
|
44
|
+
"TEXT_MUTED": ("_colors", "TEXT_MUTED"),
|
|
45
|
+
"TEXT_LIGHT": ("_colors", "TEXT_LIGHT"),
|
|
46
|
+
"TEXT_WHITE": ("_colors", "TEXT_WHITE"),
|
|
47
|
+
"ACCENT_BLUE": ("_colors", "ACCENT_BLUE"),
|
|
48
|
+
# _geometry
|
|
49
|
+
"LayoutCalculator": ("_geometry", "LayoutCalculator"),
|
|
50
|
+
"ScreenInfo": ("_geometry", "ScreenInfo"),
|
|
51
|
+
"get_primary_screen": ("_geometry", "get_primary_screen"),
|
|
52
|
+
"get_all_screens": ("_geometry", "get_all_screens"),
|
|
53
|
+
"get_screen_containing": ("_geometry", "get_screen_containing"),
|
|
54
|
+
# _text
|
|
55
|
+
"FontConfig": ("_text", "FontConfig"),
|
|
56
|
+
"FontManager": ("_text", "FontManager"),
|
|
57
|
+
"font_manager": ("_text", "font_manager"),
|
|
58
|
+
# _images
|
|
59
|
+
"ImageCache": ("_images", "ImageCache"),
|
|
60
|
+
"image_cache": ("_images", "image_cache"),
|
|
61
|
+
# _animations
|
|
62
|
+
"FadeAnimation": ("_animations", "FadeAnimation"),
|
|
63
|
+
"SlideAnimation": ("_animations", "SlideAnimation"),
|
|
64
|
+
"PropertyAnimation": ("_animations", "PropertyAnimation"),
|
|
65
|
+
# _themes
|
|
66
|
+
"Theme": ("_themes", "Theme"),
|
|
67
|
+
"THEME_DARK": ("_themes", "THEME_DARK"),
|
|
68
|
+
"THEME_LIGHT": ("_themes", "THEME_LIGHT"),
|
|
69
|
+
# _app
|
|
70
|
+
"QtApp": ("_app", "QtApp"),
|
|
71
|
+
"get_rl_monitor": ("_app", "get_rl_monitor"),
|
|
72
|
+
"snap_to_rl_monitor": ("_app", "snap_to_rl_monitor"),
|
|
73
|
+
"is_rl_active": ("_app", "is_rl_active"),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = list(_EXPORTS.keys())
|