Splatlogger 1.6.3__tar.gz → 1.6.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {splatlogger-1.6.3/src/Splatlogger.egg-info → splatlogger-1.6.4}/PKG-INFO +19 -14
- {splatlogger-1.6.3 → splatlogger-1.6.4}/README.md +16 -12
- {splatlogger-1.6.3 → splatlogger-1.6.4}/pyproject.toml +4 -3
- {splatlogger-1.6.3 → splatlogger-1.6.4/src/Splatlogger.egg-info}/PKG-INFO +19 -14
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/SOURCES.txt +1 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/requires.txt +1 -0
- splatlogger-1.6.4/src/splatlogger/__main__.py +373 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/cemu_gecko.py +4 -2
- splatlogger-1.6.4/src/splatlogger/match_logger.py +249 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/tcpgecko.py +33 -31
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/tcpgecko_aroma.py +10 -10
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/tuples.py +5 -2
- splatlogger-1.6.4/src/splatlogger/utility_functions.py +173 -0
- splatlogger-1.6.3/src/splatlogger/__main__.py +0 -404
- splatlogger-1.6.3/src/splatlogger/match_logger.py +0 -210
- {splatlogger-1.6.3 → splatlogger-1.6.4}/MANIFEST.in +0 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/setup.cfg +0 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/dependency_links.txt +0 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/entry_points.txt +0 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/top_level.txt +0 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/__init__.py +0 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/data/api.json +0 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/data/names.json +0 -0
- {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/enums.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Splatlogger
|
|
3
|
-
Version: 1.6.
|
|
4
|
-
Summary: PID (Principal ID)/
|
|
3
|
+
Version: 1.6.4
|
|
4
|
+
Summary: PID (Principal ID)/Network ID grabber and match logger for Splatoon
|
|
5
5
|
Author-email: Shadow Doggo <shadowdoggo@protonmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Source Code, https://codeberg.org/ShadowDoggo/Splatlogger
|
|
@@ -13,18 +13,15 @@ Requires-Dist: userpaths
|
|
|
13
13
|
Requires-Dist: requests
|
|
14
14
|
Requires-Dist: psutil
|
|
15
15
|
Requires-Dist: pymemoryeditor
|
|
16
|
+
Requires-Dist: colorama
|
|
16
17
|
|
|
17
18
|
# Splatlogger
|
|
18
|
-
A CLI PID (Principal ID)/
|
|
19
|
-
|
|
20
|
-
## Prerequisites
|
|
21
|
-
To install the package, you'll need to have Python 3.11 or newer installed on your system.
|
|
22
|
-
On Android devices you can use Termux.
|
|
23
|
-
Python is not required for the Windows executable.
|
|
19
|
+
A CLI PID (Principal ID)/Network ID grabber and match logger for Splatoon, written in Python.
|
|
24
20
|
|
|
25
21
|
Both the Wii U and Cemu (Windows and Linux only) are supported.
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
## Prerequisites (Wii U)
|
|
24
|
+
Your console will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew,
|
|
28
25
|
such as Haxchi or the browser exploit.
|
|
29
26
|
|
|
30
27
|
On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko), [Geckiine](https://hb-app.store/wiiu/geckiine),
|
|
@@ -33,10 +30,15 @@ or any other app with the TCPGecko server.
|
|
|
33
30
|
On Aroma, install the [TCPGecko Aroma plugin](https://codeberg.org/ShadowDoggo/TCPGeckoAroma).
|
|
34
31
|
|
|
35
32
|
## Installation
|
|
33
|
+
### Windows executable
|
|
36
34
|
On Windows, you can use the executable provided on the releases page.
|
|
37
|
-
Though if you already have Python installed, it's recommended to use the package.
|
|
35
|
+
Though if you already have Python installed, it's recommended to use the package instead.
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
### Python package
|
|
38
|
+
To use the package, you'll need to have Python 3.11 or newer installed on your system.
|
|
39
|
+
On Android devices you can use Termux.
|
|
40
|
+
|
|
41
|
+
Install the package from PyPI:
|
|
40
42
|
```
|
|
41
43
|
pip install Splatlogger
|
|
42
44
|
```
|
|
@@ -47,14 +49,14 @@ Or download the latest release from Codeberg and run:
|
|
|
47
49
|
pip install path/to/Splatlogger-v1.x.zip
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
If you do have Python installed but do not wish to install the package, you can run
|
|
52
|
+
If you do have Python installed but do not wish to install the package, you can run it from the source code.
|
|
51
53
|
|
|
52
54
|
Install the required dependencies:
|
|
53
55
|
```
|
|
54
56
|
cd path/to/Splatlogger-v1.x/ && pip install -r requirements.txt
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
Then run
|
|
59
|
+
Then run:
|
|
58
60
|
```
|
|
59
61
|
cd src/ && python -m splatlogger
|
|
60
62
|
```
|
|
@@ -76,7 +78,7 @@ Options:
|
|
|
76
78
|
- `-aroma` - Switch to Aroma mode. Enable if using the TCPGecko Aroma plugin.
|
|
77
79
|
|
|
78
80
|
- `-cemu [PID]` - Switch to Cemu mode. The `-ip` argument is not required.
|
|
79
|
-
Note: If you have ptrace protection enabled, you'll need to run Python as root. It's best to run the
|
|
81
|
+
Note: If you have ptrace protection enabled, you'll need to run Python as root. It's best to run the package from source in that case.
|
|
80
82
|
- `PID` - PID of the Cemu process (optional).
|
|
81
83
|
|
|
82
84
|
- `-log-level option` - Set how much data should be logged.
|
|
@@ -100,6 +102,9 @@ Only required for logging stats. If the default values don't work, you'll need t
|
|
|
100
102
|
|
|
101
103
|
- `-silent` - Disable printing logs to the console.
|
|
102
104
|
|
|
105
|
+
For example, `splatlogger -ip 192.168.1.100 -log-level extended -auto latest` will save an extended log of the latest match you play
|
|
106
|
+
(replace `192.168.1.100` with your actual IP address).
|
|
107
|
+
|
|
103
108
|
Logs are saved in `(User)/Documents/Splatlogger/logs/`.
|
|
104
109
|
|
|
105
110
|
To always run with the same IP and options without having to type them in manually, create an `args.txt` file in
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
# Splatlogger
|
|
2
|
-
A CLI PID (Principal ID)/
|
|
3
|
-
|
|
4
|
-
## Prerequisites
|
|
5
|
-
To install the package, you'll need to have Python 3.11 or newer installed on your system.
|
|
6
|
-
On Android devices you can use Termux.
|
|
7
|
-
Python is not required for the Windows executable.
|
|
2
|
+
A CLI PID (Principal ID)/Network ID grabber and match logger for Splatoon, written in Python.
|
|
8
3
|
|
|
9
4
|
Both the Wii U and Cemu (Windows and Linux only) are supported.
|
|
10
5
|
|
|
11
|
-
|
|
6
|
+
## Prerequisites (Wii U)
|
|
7
|
+
Your console will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew,
|
|
12
8
|
such as Haxchi or the browser exploit.
|
|
13
9
|
|
|
14
10
|
On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko), [Geckiine](https://hb-app.store/wiiu/geckiine),
|
|
@@ -17,10 +13,15 @@ or any other app with the TCPGecko server.
|
|
|
17
13
|
On Aroma, install the [TCPGecko Aroma plugin](https://codeberg.org/ShadowDoggo/TCPGeckoAroma).
|
|
18
14
|
|
|
19
15
|
## Installation
|
|
16
|
+
### Windows executable
|
|
20
17
|
On Windows, you can use the executable provided on the releases page.
|
|
21
|
-
Though if you already have Python installed, it's recommended to use the package.
|
|
18
|
+
Though if you already have Python installed, it's recommended to use the package instead.
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
### Python package
|
|
21
|
+
To use the package, you'll need to have Python 3.11 or newer installed on your system.
|
|
22
|
+
On Android devices you can use Termux.
|
|
23
|
+
|
|
24
|
+
Install the package from PyPI:
|
|
24
25
|
```
|
|
25
26
|
pip install Splatlogger
|
|
26
27
|
```
|
|
@@ -31,14 +32,14 @@ Or download the latest release from Codeberg and run:
|
|
|
31
32
|
pip install path/to/Splatlogger-v1.x.zip
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
If you do have Python installed but do not wish to install the package, you can run
|
|
35
|
+
If you do have Python installed but do not wish to install the package, you can run it from the source code.
|
|
35
36
|
|
|
36
37
|
Install the required dependencies:
|
|
37
38
|
```
|
|
38
39
|
cd path/to/Splatlogger-v1.x/ && pip install -r requirements.txt
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
Then run
|
|
42
|
+
Then run:
|
|
42
43
|
```
|
|
43
44
|
cd src/ && python -m splatlogger
|
|
44
45
|
```
|
|
@@ -60,7 +61,7 @@ Options:
|
|
|
60
61
|
- `-aroma` - Switch to Aroma mode. Enable if using the TCPGecko Aroma plugin.
|
|
61
62
|
|
|
62
63
|
- `-cemu [PID]` - Switch to Cemu mode. The `-ip` argument is not required.
|
|
63
|
-
Note: If you have ptrace protection enabled, you'll need to run Python as root. It's best to run the
|
|
64
|
+
Note: If you have ptrace protection enabled, you'll need to run Python as root. It's best to run the package from source in that case.
|
|
64
65
|
- `PID` - PID of the Cemu process (optional).
|
|
65
66
|
|
|
66
67
|
- `-log-level option` - Set how much data should be logged.
|
|
@@ -84,6 +85,9 @@ Only required for logging stats. If the default values don't work, you'll need t
|
|
|
84
85
|
|
|
85
86
|
- `-silent` - Disable printing logs to the console.
|
|
86
87
|
|
|
88
|
+
For example, `splatlogger -ip 192.168.1.100 -log-level extended -auto latest` will save an extended log of the latest match you play
|
|
89
|
+
(replace `192.168.1.100` with your actual IP address).
|
|
90
|
+
|
|
87
91
|
Logs are saved in `(User)/Documents/Splatlogger/logs/`.
|
|
88
92
|
|
|
89
93
|
To always run with the same IP and options without having to type them in manually, create an `args.txt` file in
|
|
@@ -4,18 +4,19 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "Splatlogger"
|
|
7
|
-
version = "1.6.
|
|
7
|
+
version = "1.6.4"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Shadow Doggo", email="shadowdoggo@protonmail.com" }
|
|
10
10
|
]
|
|
11
|
-
description = "PID (Principal ID)/
|
|
11
|
+
description = "PID (Principal ID)/Network ID grabber and match logger for Splatoon"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
14
14
|
dependencies = [
|
|
15
15
|
"userpaths",
|
|
16
16
|
"requests",
|
|
17
17
|
"psutil",
|
|
18
|
-
"pymemoryeditor"
|
|
18
|
+
"pymemoryeditor",
|
|
19
|
+
"colorama"
|
|
19
20
|
]
|
|
20
21
|
classifiers = [
|
|
21
22
|
"Programming Language :: Python :: 3",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Splatlogger
|
|
3
|
-
Version: 1.6.
|
|
4
|
-
Summary: PID (Principal ID)/
|
|
3
|
+
Version: 1.6.4
|
|
4
|
+
Summary: PID (Principal ID)/Network ID grabber and match logger for Splatoon
|
|
5
5
|
Author-email: Shadow Doggo <shadowdoggo@protonmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Source Code, https://codeberg.org/ShadowDoggo/Splatlogger
|
|
@@ -13,18 +13,15 @@ Requires-Dist: userpaths
|
|
|
13
13
|
Requires-Dist: requests
|
|
14
14
|
Requires-Dist: psutil
|
|
15
15
|
Requires-Dist: pymemoryeditor
|
|
16
|
+
Requires-Dist: colorama
|
|
16
17
|
|
|
17
18
|
# Splatlogger
|
|
18
|
-
A CLI PID (Principal ID)/
|
|
19
|
-
|
|
20
|
-
## Prerequisites
|
|
21
|
-
To install the package, you'll need to have Python 3.11 or newer installed on your system.
|
|
22
|
-
On Android devices you can use Termux.
|
|
23
|
-
Python is not required for the Windows executable.
|
|
19
|
+
A CLI PID (Principal ID)/Network ID grabber and match logger for Splatoon, written in Python.
|
|
24
20
|
|
|
25
21
|
Both the Wii U and Cemu (Windows and Linux only) are supported.
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
## Prerequisites (Wii U)
|
|
24
|
+
Your console will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew,
|
|
28
25
|
such as Haxchi or the browser exploit.
|
|
29
26
|
|
|
30
27
|
On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko), [Geckiine](https://hb-app.store/wiiu/geckiine),
|
|
@@ -33,10 +30,15 @@ or any other app with the TCPGecko server.
|
|
|
33
30
|
On Aroma, install the [TCPGecko Aroma plugin](https://codeberg.org/ShadowDoggo/TCPGeckoAroma).
|
|
34
31
|
|
|
35
32
|
## Installation
|
|
33
|
+
### Windows executable
|
|
36
34
|
On Windows, you can use the executable provided on the releases page.
|
|
37
|
-
Though if you already have Python installed, it's recommended to use the package.
|
|
35
|
+
Though if you already have Python installed, it's recommended to use the package instead.
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
### Python package
|
|
38
|
+
To use the package, you'll need to have Python 3.11 or newer installed on your system.
|
|
39
|
+
On Android devices you can use Termux.
|
|
40
|
+
|
|
41
|
+
Install the package from PyPI:
|
|
40
42
|
```
|
|
41
43
|
pip install Splatlogger
|
|
42
44
|
```
|
|
@@ -47,14 +49,14 @@ Or download the latest release from Codeberg and run:
|
|
|
47
49
|
pip install path/to/Splatlogger-v1.x.zip
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
If you do have Python installed but do not wish to install the package, you can run
|
|
52
|
+
If you do have Python installed but do not wish to install the package, you can run it from the source code.
|
|
51
53
|
|
|
52
54
|
Install the required dependencies:
|
|
53
55
|
```
|
|
54
56
|
cd path/to/Splatlogger-v1.x/ && pip install -r requirements.txt
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
Then run
|
|
59
|
+
Then run:
|
|
58
60
|
```
|
|
59
61
|
cd src/ && python -m splatlogger
|
|
60
62
|
```
|
|
@@ -76,7 +78,7 @@ Options:
|
|
|
76
78
|
- `-aroma` - Switch to Aroma mode. Enable if using the TCPGecko Aroma plugin.
|
|
77
79
|
|
|
78
80
|
- `-cemu [PID]` - Switch to Cemu mode. The `-ip` argument is not required.
|
|
79
|
-
Note: If you have ptrace protection enabled, you'll need to run Python as root. It's best to run the
|
|
81
|
+
Note: If you have ptrace protection enabled, you'll need to run Python as root. It's best to run the package from source in that case.
|
|
80
82
|
- `PID` - PID of the Cemu process (optional).
|
|
81
83
|
|
|
82
84
|
- `-log-level option` - Set how much data should be logged.
|
|
@@ -100,6 +102,9 @@ Only required for logging stats. If the default values don't work, you'll need t
|
|
|
100
102
|
|
|
101
103
|
- `-silent` - Disable printing logs to the console.
|
|
102
104
|
|
|
105
|
+
For example, `splatlogger -ip 192.168.1.100 -log-level extended -auto latest` will save an extended log of the latest match you play
|
|
106
|
+
(replace `192.168.1.100` with your actual IP address).
|
|
107
|
+
|
|
103
108
|
Logs are saved in `(User)/Documents/Splatlogger/logs/`.
|
|
104
109
|
|
|
105
110
|
To always run with the same IP and options without having to type them in manually, create an `args.txt` file in
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# Splatlogger (c) 2025 Shadow Doggo.
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import binascii
|
|
5
|
+
import ipaddress
|
|
6
|
+
import json
|
|
7
|
+
import platform
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import webbrowser
|
|
11
|
+
from base64 import b64decode
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from importlib import resources
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
import userpaths # type: ignore[import-untyped]
|
|
18
|
+
from colorama import Fore, Style, just_fix_windows_console
|
|
19
|
+
|
|
20
|
+
from . import utility_functions as uf
|
|
21
|
+
from .cemu_gecko import CemuGecko, CemuGeckoError
|
|
22
|
+
from .enums import Pointers
|
|
23
|
+
from .match_logger import MatchLogger
|
|
24
|
+
from .tcpgecko import TCPGecko, GeckoConnectionFailedError
|
|
25
|
+
from .tcpgecko_aroma import TCPGeckoAroma
|
|
26
|
+
from .tuples import Options, AccountInfo, PlayerInfo
|
|
27
|
+
|
|
28
|
+
VERSION = "1.6.4"
|
|
29
|
+
OPTIONS: Options
|
|
30
|
+
GECKO: TCPGecko | TCPGeckoAroma | CemuGecko
|
|
31
|
+
STATIC_MEM_ADR: int
|
|
32
|
+
API: dict[str, str]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main() -> None:
|
|
36
|
+
if platform.system() == "Windows":
|
|
37
|
+
just_fix_windows_console()
|
|
38
|
+
|
|
39
|
+
print(f"Splatlogger v{VERSION} by Shadow Doggo\n")
|
|
40
|
+
|
|
41
|
+
global API, OPTIONS, GECKO, STATIC_MEM_ADR
|
|
42
|
+
API = get_api_keys()
|
|
43
|
+
OPTIONS = parse_args()
|
|
44
|
+
GECKO = create_tcpgecko_instance()
|
|
45
|
+
|
|
46
|
+
uf.set_consts(OPTIONS, GECKO)
|
|
47
|
+
|
|
48
|
+
STATIC_MEM_ADR = get_static_mem_adr()
|
|
49
|
+
|
|
50
|
+
uf.print_success("Success.\n" if OPTIONS.cemu else "Connected.\n")
|
|
51
|
+
|
|
52
|
+
if match_logger := create_match_logger_instance():
|
|
53
|
+
if OPTIONS.auto_logging:
|
|
54
|
+
print("Auto logging enabled.")
|
|
55
|
+
auto_log(match_logger)
|
|
56
|
+
else:
|
|
57
|
+
uf.create_log_file(match_logger)
|
|
58
|
+
splatlog(match_logger, match_count=1)
|
|
59
|
+
else:
|
|
60
|
+
splatlog(None, match_count=1)
|
|
61
|
+
|
|
62
|
+
GECKO.close()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_api_keys() -> dict[str, str]:
|
|
66
|
+
try:
|
|
67
|
+
api_path = resources.files(__package__).joinpath("data", "api.json")
|
|
68
|
+
with api_path.open("r", encoding="utf-8") as api_f:
|
|
69
|
+
return json.load(api_f)
|
|
70
|
+
except FileNotFoundError:
|
|
71
|
+
uf.print_error("Couldn't find 'api.json'. The package may have been installed incorrectly.")
|
|
72
|
+
sys.exit()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def parse_args() -> Options:
|
|
76
|
+
parser = argparse.ArgumentParser(description="PID (Principal ID)/Network ID grabber and match logger for Splatoon")
|
|
77
|
+
add_parser_arguments(parser)
|
|
78
|
+
|
|
79
|
+
if (args_path := Path(userpaths.get_my_documents()) / "Splatlogger/args.txt").is_file():
|
|
80
|
+
with open(args_path, encoding="utf-8") as args_f:
|
|
81
|
+
args = parser.parse_args(args_f.read().split())
|
|
82
|
+
else:
|
|
83
|
+
args = parser.parse_args()
|
|
84
|
+
|
|
85
|
+
if len(sys.argv) == 1 and not args_path.is_file():
|
|
86
|
+
print(f'{Fore.YELLOW}No arguments were provided. Run "splatlogger -h" for help.{Style.RESET_ALL}'
|
|
87
|
+
"\nWould you like to open the README? (y/n)")
|
|
88
|
+
if input().lower() in ("y", "yes"):
|
|
89
|
+
webbrowser.open("https://codeberg.org/ShadowDoggo/Splatlogger/src/branch/main/README.md")
|
|
90
|
+
|
|
91
|
+
sys.exit()
|
|
92
|
+
|
|
93
|
+
if args.aroma and args.cemu:
|
|
94
|
+
uf.print_error("Cannot use both Aroma and Cemu modes.")
|
|
95
|
+
sys.exit()
|
|
96
|
+
|
|
97
|
+
stack_addr: int | None = None
|
|
98
|
+
if args.stack and args.cemu:
|
|
99
|
+
try:
|
|
100
|
+
stack_addr = int(args.stack, 16)
|
|
101
|
+
except ValueError:
|
|
102
|
+
uf.print_error("Invalid stack address. Please provide a valid hex value.")
|
|
103
|
+
sys.exit()
|
|
104
|
+
|
|
105
|
+
if args.log_level == "none" and args.silent:
|
|
106
|
+
uf.print_warning("You have disabled printing logs to the console as well as creating a log file. "
|
|
107
|
+
"Are you sure this wasn't a mistake?\n")
|
|
108
|
+
|
|
109
|
+
return Options(ip=args.ip, log_level=args.log_level, auto_logging=args.auto in ("all", "latest"),
|
|
110
|
+
log_latest=args.auto == "latest", aroma=args.aroma, cemu=args.cemu is not None, cemu_pid=args.cemu,
|
|
111
|
+
ptr_offset=0x503000 if args.cemu else 0, spfn=args.spfn, no_pnid=args.no_pnid,
|
|
112
|
+
silent_logging=args.silent, stack=stack_addr)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def add_parser_arguments(parser: argparse.ArgumentParser) -> None:
|
|
116
|
+
parser.add_argument("-ip", help="Your Wii U's LAN IP address.")
|
|
117
|
+
parser.add_argument("-aroma", help="Switch to Aroma mode. Enable if using the TCPGecko Aroma plugin.",
|
|
118
|
+
action="store_true")
|
|
119
|
+
parser.add_argument("-cemu",
|
|
120
|
+
help="Switch to Cemu mode (optional: PID of the Cemu process).", metavar="PID",
|
|
121
|
+
nargs="?", const=-1)
|
|
122
|
+
parser.add_argument("-log-level", help="Set how much data should be logged (see README).",
|
|
123
|
+
choices=["none", "standard", "extended", "stats"], default="standard")
|
|
124
|
+
parser.add_argument("-auto",
|
|
125
|
+
help="Enable auto logging (see README).",
|
|
126
|
+
choices=["all", "latest"], nargs="?", const="all")
|
|
127
|
+
parser.add_argument("-stack",
|
|
128
|
+
help="Beginning address of the stack space for Default Core 1 (Cemu only, see README).",
|
|
129
|
+
metavar="Address (in hex)")
|
|
130
|
+
parser.add_argument("-spfn", help="Fetch SFID instead of PNID. Enable if playing on SPFN.",
|
|
131
|
+
action="store_true")
|
|
132
|
+
parser.add_argument("-no-pnid", help="Disable fetching PNIDs.", action="store_true")
|
|
133
|
+
parser.add_argument("-silent", help="Disable printing logs to the console.", action="store_true")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def create_tcpgecko_instance() -> TCPGecko | TCPGeckoAroma | CemuGecko:
|
|
137
|
+
try:
|
|
138
|
+
if OPTIONS.cemu:
|
|
139
|
+
print("Opening Cemu process...")
|
|
140
|
+
return CemuGecko(OPTIONS.cemu_pid)
|
|
141
|
+
|
|
142
|
+
ipaddress.ip_address(OPTIONS.ip)
|
|
143
|
+
print("Connecting to:", OPTIONS.ip)
|
|
144
|
+
return TCPGeckoAroma(OPTIONS.ip) if OPTIONS.aroma else TCPGecko(OPTIONS.ip)
|
|
145
|
+
except ValueError:
|
|
146
|
+
uf.print_error("Please provide a valid IP address.")
|
|
147
|
+
sys.exit()
|
|
148
|
+
except (GeckoConnectionFailedError, CemuGeckoError) as e:
|
|
149
|
+
uf.print_error(str(e))
|
|
150
|
+
sys.exit()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@uf.handle_gecko_errors
|
|
154
|
+
def get_static_mem_adr() -> int:
|
|
155
|
+
static_mem_adr = GECKO.peek32(Pointers.STATIC_MEM - OPTIONS.ptr_offset)
|
|
156
|
+
if static_mem_adr == 0:
|
|
157
|
+
uf.exit_with_close(
|
|
158
|
+
f"\n{Fore.YELLOW}Could not find StaticMem address."
|
|
159
|
+
"\nIf you're on Cemu, this is likely due to the program not being able to read process memory"
|
|
160
|
+
f"due to ptrace protection.{Style.RESET_ALL}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return static_mem_adr
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def create_match_logger_instance() -> MatchLogger | None:
|
|
167
|
+
if OPTIONS.log_level != "none":
|
|
168
|
+
try:
|
|
169
|
+
return MatchLogger(OPTIONS, GECKO, STATIC_MEM_ADR)
|
|
170
|
+
except FileNotFoundError:
|
|
171
|
+
uf.exit_with_close("Couldn't find 'names.json'. The package may have been installed incorrectly.")
|
|
172
|
+
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@uf.handle_gecko_errors_and_kb_interrupt
|
|
177
|
+
def auto_log(match_logger: MatchLogger) -> None:
|
|
178
|
+
match_in_progress = False
|
|
179
|
+
count = 0
|
|
180
|
+
while True:
|
|
181
|
+
if not uf.is_player_in_vs_match():
|
|
182
|
+
match_in_progress = False
|
|
183
|
+
|
|
184
|
+
if not match_in_progress and uf.is_player_in_vs_match():
|
|
185
|
+
match_in_progress = True
|
|
186
|
+
count += 1
|
|
187
|
+
|
|
188
|
+
uf.create_log_file(match_logger)
|
|
189
|
+
uf.print_if_not_silent(f"\nMatch {count}\n")
|
|
190
|
+
time.sleep(1)
|
|
191
|
+
|
|
192
|
+
splatlog(match_logger, count)
|
|
193
|
+
|
|
194
|
+
time.sleep(10)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@uf.handle_gecko_errors
|
|
198
|
+
def splatlog(match_logger: MatchLogger | None, match_count: int) -> None:
|
|
199
|
+
date = datetime.now()
|
|
200
|
+
adrs = uf.get_common_addresses(STATIC_MEM_ADR)
|
|
201
|
+
session_id = uf.get_session_id()
|
|
202
|
+
player_count = uf.get_player_count(adrs.main_mgr_base_adr)
|
|
203
|
+
|
|
204
|
+
print(uf.player_table_header(OPTIONS.spfn))
|
|
205
|
+
|
|
206
|
+
player_info_list, request_errors = collect_player_info(player_count, adrs.player_info_ary_adr)
|
|
207
|
+
|
|
208
|
+
uf.print_if_not_silent("")
|
|
209
|
+
if request_errors:
|
|
210
|
+
uf.print_if_not_silent(f"Failed to get account data for one or more players: {'; '.join(request_errors)}\n")
|
|
211
|
+
|
|
212
|
+
uf.print_if_not_silent(f"Session ID: {session_id}")
|
|
213
|
+
uf.print_if_not_silent(f"Fetched at: {date.strftime('%Y-%m-%d %H:%M:%S')} (UTC {uf.get_utc_offset(date):+})")
|
|
214
|
+
|
|
215
|
+
if match_logger:
|
|
216
|
+
match_logger.log_match(date, session_id, match_count, player_info_list, adrs)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def collect_player_info(player_count: int, player_info_ary_adr: int) -> tuple[list[PlayerInfo], list[str]]:
|
|
220
|
+
request_errors: list[str] = []
|
|
221
|
+
player_info_list: list[PlayerInfo] = []
|
|
222
|
+
|
|
223
|
+
for player_idx in range(player_count):
|
|
224
|
+
if player_info_tuple := get_player_info(player_idx, player_info_ary_adr):
|
|
225
|
+
player_info, act_info = player_info_tuple
|
|
226
|
+
else:
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
if act_info.request_error:
|
|
230
|
+
request_errors.append(act_info.request_error)
|
|
231
|
+
|
|
232
|
+
player_info_list.append(player_info)
|
|
233
|
+
print(uf.player_table_entry(player_info, act_info))
|
|
234
|
+
|
|
235
|
+
return player_info_list, request_errors
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_player_info(player_idx: int, player_info_ary_adr: int) -> tuple[PlayerInfo, AccountInfo] | None:
|
|
239
|
+
player_info_adr = GECKO.peek32(player_info_ary_adr + player_idx * 0x4)
|
|
240
|
+
if player_info_adr == 0:
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
player_info_raw = GECKO.peek_raw(player_info_adr, length=0xD4)
|
|
244
|
+
player_pid = uf.read_int(player_info_raw, 0xD0, 0xD4)
|
|
245
|
+
|
|
246
|
+
if player_pid == 0:
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
player_name = decode_player_name(player_info_raw)
|
|
250
|
+
act_info = get_player_account_info(player_pid, player_name)
|
|
251
|
+
player_info = build_player_info(player_idx, player_pid, player_name, act_info, player_info_raw)
|
|
252
|
+
|
|
253
|
+
return player_info, act_info
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def decode_player_name(player_info_raw: bytes) -> str:
|
|
257
|
+
return (player_info_raw[0x6:0x26]
|
|
258
|
+
.decode("utf-16-be", errors="replace")
|
|
259
|
+
.split("\u0000")[0]
|
|
260
|
+
.replace("\n", "")
|
|
261
|
+
.replace("\r", ""))
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def get_player_account_info(player_pid: int, player_name: str) -> AccountInfo:
|
|
265
|
+
if OPTIONS.no_pnid:
|
|
266
|
+
return AccountInfo("", player_name, "")
|
|
267
|
+
|
|
268
|
+
act_info = get_spfn_account_info(player_pid) if OPTIONS.spfn else get_pn_account_info(player_pid)
|
|
269
|
+
if act_info.request_error:
|
|
270
|
+
return AccountInfo("", player_name, act_info.request_error)
|
|
271
|
+
|
|
272
|
+
return act_info
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def build_player_info(player_idx: int, player_pid: int, player_name: str, act_info: AccountInfo,
|
|
276
|
+
player_info_raw: bytes) -> PlayerInfo:
|
|
277
|
+
return PlayerInfo(
|
|
278
|
+
idx=player_idx,
|
|
279
|
+
pid=player_pid,
|
|
280
|
+
network_id=act_info.network_id,
|
|
281
|
+
name=player_name,
|
|
282
|
+
mii_name=act_info.mii_name,
|
|
283
|
+
team=uf.read_int(player_info_raw, 0x33, 0x34),
|
|
284
|
+
gender=uf.read_int(player_info_raw, 0x37, 0x38),
|
|
285
|
+
skin_tone=uf.read_int(player_info_raw, 0x3B, 0x3C),
|
|
286
|
+
eye_color=uf.read_int(player_info_raw, 0x3F, 0x40),
|
|
287
|
+
weapon=uf.read_int(player_info_raw, 0x46, 0x48),
|
|
288
|
+
sub_weapon=uf.read_int(player_info_raw, 0x4A, 0x4C),
|
|
289
|
+
special_weapon=uf.read_int(player_info_raw, 0x4D, 0x50),
|
|
290
|
+
shoes=uf.read_int(player_info_raw, 0x54, 0x58),
|
|
291
|
+
clothes=uf.read_int(player_info_raw, 0x70, 0x74),
|
|
292
|
+
headgear=uf.read_int(player_info_raw, 0x8C, 0x90),
|
|
293
|
+
level=uf.read_int(player_info_raw, 0xAF, 0xB0, signed=True) + 1,
|
|
294
|
+
rank=uf.read_int(player_info_raw, 0xB3, 0xB4, signed=True)
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def get_pn_account_info(player_pid: int) -> AccountInfo:
|
|
299
|
+
user_id = ""
|
|
300
|
+
mii_name = ""
|
|
301
|
+
request_error = ""
|
|
302
|
+
|
|
303
|
+
for _ in range(3):
|
|
304
|
+
try:
|
|
305
|
+
response = uf.process_response(requests.get(
|
|
306
|
+
f"https://account.pretendo.cc/v1/api/miis?pids={player_pid}",
|
|
307
|
+
headers={"X-Nintendo-Client-ID": API["client_id"],
|
|
308
|
+
"X-Nintendo-Client-Secret": API["client_secret"]},
|
|
309
|
+
timeout=5
|
|
310
|
+
), spfn=False)
|
|
311
|
+
except requests.RequestException as ex:
|
|
312
|
+
request_error = str(ex)
|
|
313
|
+
time.sleep(1)
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
if response is None:
|
|
317
|
+
request_error = "Got empty or invalid response."
|
|
318
|
+
time.sleep(1)
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
user_id = response[0]
|
|
322
|
+
mii_name = response[1].replace("\n", "").replace("\r", "")
|
|
323
|
+
|
|
324
|
+
request_error = ""
|
|
325
|
+
break
|
|
326
|
+
|
|
327
|
+
return AccountInfo(user_id, mii_name, request_error)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def get_spfn_account_info(player_pid: int) -> AccountInfo:
|
|
331
|
+
user_id = ""
|
|
332
|
+
mii_name = ""
|
|
333
|
+
request_error = ""
|
|
334
|
+
|
|
335
|
+
for _ in range(3):
|
|
336
|
+
try:
|
|
337
|
+
response = uf.process_response(requests.get(
|
|
338
|
+
f"https://account.spfn.net/v1/api/miis?pids={player_pid}",
|
|
339
|
+
timeout=5
|
|
340
|
+
), spfn=True)
|
|
341
|
+
mii_data_response = requests.get(
|
|
342
|
+
f"https://account.spfn.net/api/v2/users/{player_pid}/mii",
|
|
343
|
+
timeout=5
|
|
344
|
+
)
|
|
345
|
+
except requests.RequestException as ex:
|
|
346
|
+
request_error = str(ex)
|
|
347
|
+
time.sleep(1)
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
if None in (response, mii_data_response):
|
|
351
|
+
request_error = "Got empty or invalid response."
|
|
352
|
+
time.sleep(1)
|
|
353
|
+
continue
|
|
354
|
+
|
|
355
|
+
user_id = response[0] # type: ignore[index]
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
mii_data = b64decode(
|
|
359
|
+
mii_data_response.text.replace("\\n", "").replace("\\r", ""))
|
|
360
|
+
mii_name = mii_data.decode("utf-16")[13:23]
|
|
361
|
+
except (binascii.Error, UnicodeDecodeError):
|
|
362
|
+
request_error = "Got empty or invalid response."
|
|
363
|
+
time.sleep(1)
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
request_error = ""
|
|
367
|
+
break
|
|
368
|
+
|
|
369
|
+
return AccountInfo(user_id, mii_name, request_error)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
if __name__ == "__main__":
|
|
373
|
+
main()
|
|
@@ -5,13 +5,15 @@ import struct
|
|
|
5
5
|
import psutil
|
|
6
6
|
from PyMemoryEditor import OpenProcess, ProcessIDNotExistsError # type: ignore[import-untyped]
|
|
7
7
|
|
|
8
|
+
from .tcpgecko import TCPGeckoError
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
class CemuGecko:
|
|
10
12
|
"""Wrapper around PyMemoryEditor to provide Cemu memory access.
|
|
11
13
|
Compatible with existing code written for TCPGecko.
|
|
12
14
|
"""
|
|
13
15
|
|
|
14
|
-
def __init__(self, pid: int
|
|
16
|
+
def __init__(self, pid: int):
|
|
15
17
|
cemu_pid = self._find_cemu_process() if pid == -1 else pid
|
|
16
18
|
if cemu_pid is None:
|
|
17
19
|
raise CemuGeckoError("Cemu process not found.")
|
|
@@ -83,4 +85,4 @@ class CemuGecko:
|
|
|
83
85
|
return None
|
|
84
86
|
|
|
85
87
|
|
|
86
|
-
class CemuGeckoError(
|
|
88
|
+
class CemuGeckoError(TCPGeckoError): pass
|