Splatlogger 1.5__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.5/LICENSE +18 -0
- splatlogger-1.5/PKG-INFO +67 -0
- splatlogger-1.5/README.md +51 -0
- splatlogger-1.5/pyproject.toml +33 -0
- splatlogger-1.5/setup.cfg +4 -0
- splatlogger-1.5/src/Splatlogger.egg-info/PKG-INFO +67 -0
- splatlogger-1.5/src/Splatlogger.egg-info/SOURCES.txt +15 -0
- splatlogger-1.5/src/Splatlogger.egg-info/dependency_links.txt +1 -0
- splatlogger-1.5/src/Splatlogger.egg-info/entry_points.txt +2 -0
- splatlogger-1.5/src/Splatlogger.egg-info/requires.txt +2 -0
- splatlogger-1.5/src/Splatlogger.egg-info/top_level.txt +1 -0
- splatlogger-1.5/src/splatlogger/__init__.py +0 -0
- splatlogger-1.5/src/splatlogger/__main__.py +286 -0
- splatlogger-1.5/src/splatlogger/data.py +774 -0
- splatlogger-1.5/src/splatlogger/match_logger.py +156 -0
- splatlogger-1.5/src/splatlogger/tcpgecko.py +196 -0
- splatlogger-1.5/src/splatlogger/tcpgecko_aroma.py +132 -0
splatlogger-1.5/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 Shadow Doggo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
6
|
+
and associated documentation files (the “Software”), to deal in the Software without
|
|
7
|
+
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
|
9
|
+
Software is furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
|
12
|
+
substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
|
15
|
+
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
16
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
17
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
splatlogger-1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Splatlogger
|
|
3
|
+
Version: 1.5
|
|
4
|
+
Summary: A PID/PNID grabber and match logger for Splatoon using TCPGecko.
|
|
5
|
+
Author-email: Shadow Doggo <shadowdoggo@protonmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Source Code, https://github.com/spoongaming61/Splatlogger
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: userpaths
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Splatlogger
|
|
18
|
+
A PID/PNID grabber and match logger for Splatoon using TCPGecko, written in Python.
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
You'll need a modded Wii U running either Tiramisu or Aroma, as well as Python 3.11 or newer installed on your system (on Android devices you can use [Termux](https://termux.com/)).
|
|
22
|
+
|
|
23
|
+
On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko) (Geckiine or SDGeckiine will work as well).
|
|
24
|
+
|
|
25
|
+
On Aroma, install the [TCPGecko Aroma plugin](https://github.com/spoongaming61/TCPGeckoAroma).
|
|
26
|
+
|
|
27
|
+
## Installation and Usage
|
|
28
|
+
Install the package from PyPI:
|
|
29
|
+
|
|
30
|
+
`python -m pip install Splatlogger`
|
|
31
|
+
|
|
32
|
+
Or download and install the latest release from GitHub:
|
|
33
|
+
|
|
34
|
+
`python -m pip install /path/to/Splatlogger-1.x.zip`
|
|
35
|
+
|
|
36
|
+
Run Splatlogger with `splatlogger -ip IP [options]` (or `python -m splatlogger -ip IP [options]`) where `IP` is your Wii U's LAN IP address.
|
|
37
|
+
|
|
38
|
+
Alternatively, you can run the script directly from the source code:
|
|
39
|
+
|
|
40
|
+
`cd /path/to/Splatlogger/src && python -m splatlogger -ip IP [options]`
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
- `-log-level [option]` - Set how much data should be logged.
|
|
44
|
+
- `none` - Don't create a log file (default).
|
|
45
|
+
- `basic` - Log only basic player information (name, PID, PNID, region).
|
|
46
|
+
- `full` - Log all player information (basic + team, level, rank, appearance, gear, weapons).
|
|
47
|
+
- `stats` - Log all player information and player stats (points, kills, deaths). Requires the match to end to finish logging.
|
|
48
|
+
|
|
49
|
+
- `-auto [option]` - Enable auto logging. When enabled will automatically log every match you play (log level must be at least `basic`).
|
|
50
|
+
- `all` - Save a log of all matches you play (default).
|
|
51
|
+
- `latest` - Save a log of only the latest match
|
|
52
|
+
|
|
53
|
+
- `-aroma` - Enable Aroma mode.
|
|
54
|
+
|
|
55
|
+
- `-silent` - Disable printing logs to the console.
|
|
56
|
+
|
|
57
|
+
Logs are saved in `/[User]/Documents/Splatlogger/logs/[Date]`.
|
|
58
|
+
|
|
59
|
+
To always run with the same IP and options, create an `args.txt` file in `/[User]/Documents/Splatlogger` and put all the arguments in there.
|
|
60
|
+
Afterward run Splatlogger without any arguments.
|
|
61
|
+
|
|
62
|
+
Only one program can be connected to TCPGecko at a time. If you have something else connected, disconnect it beforehand.
|
|
63
|
+
|
|
64
|
+
## Credits
|
|
65
|
+
[pyGecko](https://github.com/wiiudev/pyGecko) authors - tcpgecko.py
|
|
66
|
+
|
|
67
|
+
Everyone who contributed to [PNIDGrab](https://github.com/JerrySM64/PNIDGrab) and other similar PID grabbers as this is partly based on those.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Splatlogger
|
|
2
|
+
A PID/PNID grabber and match logger for Splatoon using TCPGecko, written in Python.
|
|
3
|
+
|
|
4
|
+
## Prerequisites
|
|
5
|
+
You'll need a modded Wii U running either Tiramisu or Aroma, as well as Python 3.11 or newer installed on your system (on Android devices you can use [Termux](https://termux.com/)).
|
|
6
|
+
|
|
7
|
+
On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko) (Geckiine or SDGeckiine will work as well).
|
|
8
|
+
|
|
9
|
+
On Aroma, install the [TCPGecko Aroma plugin](https://github.com/spoongaming61/TCPGeckoAroma).
|
|
10
|
+
|
|
11
|
+
## Installation and Usage
|
|
12
|
+
Install the package from PyPI:
|
|
13
|
+
|
|
14
|
+
`python -m pip install Splatlogger`
|
|
15
|
+
|
|
16
|
+
Or download and install the latest release from GitHub:
|
|
17
|
+
|
|
18
|
+
`python -m pip install /path/to/Splatlogger-1.x.zip`
|
|
19
|
+
|
|
20
|
+
Run Splatlogger with `splatlogger -ip IP [options]` (or `python -m splatlogger -ip IP [options]`) where `IP` is your Wii U's LAN IP address.
|
|
21
|
+
|
|
22
|
+
Alternatively, you can run the script directly from the source code:
|
|
23
|
+
|
|
24
|
+
`cd /path/to/Splatlogger/src && python -m splatlogger -ip IP [options]`
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
- `-log-level [option]` - Set how much data should be logged.
|
|
28
|
+
- `none` - Don't create a log file (default).
|
|
29
|
+
- `basic` - Log only basic player information (name, PID, PNID, region).
|
|
30
|
+
- `full` - Log all player information (basic + team, level, rank, appearance, gear, weapons).
|
|
31
|
+
- `stats` - Log all player information and player stats (points, kills, deaths). Requires the match to end to finish logging.
|
|
32
|
+
|
|
33
|
+
- `-auto [option]` - Enable auto logging. When enabled will automatically log every match you play (log level must be at least `basic`).
|
|
34
|
+
- `all` - Save a log of all matches you play (default).
|
|
35
|
+
- `latest` - Save a log of only the latest match
|
|
36
|
+
|
|
37
|
+
- `-aroma` - Enable Aroma mode.
|
|
38
|
+
|
|
39
|
+
- `-silent` - Disable printing logs to the console.
|
|
40
|
+
|
|
41
|
+
Logs are saved in `/[User]/Documents/Splatlogger/logs/[Date]`.
|
|
42
|
+
|
|
43
|
+
To always run with the same IP and options, create an `args.txt` file in `/[User]/Documents/Splatlogger` and put all the arguments in there.
|
|
44
|
+
Afterward run Splatlogger without any arguments.
|
|
45
|
+
|
|
46
|
+
Only one program can be connected to TCPGecko at a time. If you have something else connected, disconnect it beforehand.
|
|
47
|
+
|
|
48
|
+
## Credits
|
|
49
|
+
[pyGecko](https://github.com/wiiudev/pyGecko) authors - tcpgecko.py
|
|
50
|
+
|
|
51
|
+
Everyone who contributed to [PNIDGrab](https://github.com/JerrySM64/PNIDGrab) and other similar PID grabbers as this is partly based on those.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "Splatlogger"
|
|
7
|
+
version = "1.5"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Shadow Doggo", email="shadowdoggo@protonmail.com" }
|
|
10
|
+
]
|
|
11
|
+
description = "A PID/PNID grabber and match logger for Splatoon using TCPGecko."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"userpaths",
|
|
16
|
+
"requests"
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
]
|
|
22
|
+
license = "MIT"
|
|
23
|
+
license-files = ["LICENSE"]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
"Source Code" = "https://github.com/spoongaming61/Splatlogger"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
splatlogger = "splatlogger.__main__:main"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
include = ["splatlogger*"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Splatlogger
|
|
3
|
+
Version: 1.5
|
|
4
|
+
Summary: A PID/PNID grabber and match logger for Splatoon using TCPGecko.
|
|
5
|
+
Author-email: Shadow Doggo <shadowdoggo@protonmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Source Code, https://github.com/spoongaming61/Splatlogger
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: userpaths
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Splatlogger
|
|
18
|
+
A PID/PNID grabber and match logger for Splatoon using TCPGecko, written in Python.
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
You'll need a modded Wii U running either Tiramisu or Aroma, as well as Python 3.11 or newer installed on your system (on Android devices you can use [Termux](https://termux.com/)).
|
|
22
|
+
|
|
23
|
+
On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko) (Geckiine or SDGeckiine will work as well).
|
|
24
|
+
|
|
25
|
+
On Aroma, install the [TCPGecko Aroma plugin](https://github.com/spoongaming61/TCPGeckoAroma).
|
|
26
|
+
|
|
27
|
+
## Installation and Usage
|
|
28
|
+
Install the package from PyPI:
|
|
29
|
+
|
|
30
|
+
`python -m pip install Splatlogger`
|
|
31
|
+
|
|
32
|
+
Or download and install the latest release from GitHub:
|
|
33
|
+
|
|
34
|
+
`python -m pip install /path/to/Splatlogger-1.x.zip`
|
|
35
|
+
|
|
36
|
+
Run Splatlogger with `splatlogger -ip IP [options]` (or `python -m splatlogger -ip IP [options]`) where `IP` is your Wii U's LAN IP address.
|
|
37
|
+
|
|
38
|
+
Alternatively, you can run the script directly from the source code:
|
|
39
|
+
|
|
40
|
+
`cd /path/to/Splatlogger/src && python -m splatlogger -ip IP [options]`
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
- `-log-level [option]` - Set how much data should be logged.
|
|
44
|
+
- `none` - Don't create a log file (default).
|
|
45
|
+
- `basic` - Log only basic player information (name, PID, PNID, region).
|
|
46
|
+
- `full` - Log all player information (basic + team, level, rank, appearance, gear, weapons).
|
|
47
|
+
- `stats` - Log all player information and player stats (points, kills, deaths). Requires the match to end to finish logging.
|
|
48
|
+
|
|
49
|
+
- `-auto [option]` - Enable auto logging. When enabled will automatically log every match you play (log level must be at least `basic`).
|
|
50
|
+
- `all` - Save a log of all matches you play (default).
|
|
51
|
+
- `latest` - Save a log of only the latest match
|
|
52
|
+
|
|
53
|
+
- `-aroma` - Enable Aroma mode.
|
|
54
|
+
|
|
55
|
+
- `-silent` - Disable printing logs to the console.
|
|
56
|
+
|
|
57
|
+
Logs are saved in `/[User]/Documents/Splatlogger/logs/[Date]`.
|
|
58
|
+
|
|
59
|
+
To always run with the same IP and options, create an `args.txt` file in `/[User]/Documents/Splatlogger` and put all the arguments in there.
|
|
60
|
+
Afterward run Splatlogger without any arguments.
|
|
61
|
+
|
|
62
|
+
Only one program can be connected to TCPGecko at a time. If you have something else connected, disconnect it beforehand.
|
|
63
|
+
|
|
64
|
+
## Credits
|
|
65
|
+
[pyGecko](https://github.com/wiiudev/pyGecko) authors - tcpgecko.py
|
|
66
|
+
|
|
67
|
+
Everyone who contributed to [PNIDGrab](https://github.com/JerrySM64/PNIDGrab) and other similar PID grabbers as this is partly based on those.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/Splatlogger.egg-info/PKG-INFO
|
|
5
|
+
src/Splatlogger.egg-info/SOURCES.txt
|
|
6
|
+
src/Splatlogger.egg-info/dependency_links.txt
|
|
7
|
+
src/Splatlogger.egg-info/entry_points.txt
|
|
8
|
+
src/Splatlogger.egg-info/requires.txt
|
|
9
|
+
src/Splatlogger.egg-info/top_level.txt
|
|
10
|
+
src/splatlogger/__init__.py
|
|
11
|
+
src/splatlogger/__main__.py
|
|
12
|
+
src/splatlogger/data.py
|
|
13
|
+
src/splatlogger/match_logger.py
|
|
14
|
+
src/splatlogger/tcpgecko.py
|
|
15
|
+
src/splatlogger/tcpgecko_aroma.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
splatlogger
|
|
File without changes
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Splatlogger (c) 2025 Shadow Doggo.
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import argparse
|
|
7
|
+
import requests
|
|
8
|
+
import ipaddress
|
|
9
|
+
import userpaths # type: ignore[import-untyped]
|
|
10
|
+
import xml.etree.ElementTree as ElementTree
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
from .data import Pointers, Offsets, PlayerInfo
|
|
14
|
+
from .match_logger import MatchLogger
|
|
15
|
+
from .tcpgecko import TCPGecko, TCPGeckoException
|
|
16
|
+
from .tcpgecko_aroma import TCPGeckoAroma
|
|
17
|
+
|
|
18
|
+
_VERSION: str = "1.5"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
def splatlog(match_count: int) -> None:
|
|
23
|
+
request_fail: bool = False
|
|
24
|
+
request_errors: list[str] = []
|
|
25
|
+
session_id: int = 0
|
|
26
|
+
player_count: int = 8
|
|
27
|
+
player_info_list: list[PlayerInfo] = []
|
|
28
|
+
|
|
29
|
+
if session_adr != 0:
|
|
30
|
+
session_id_idx: int = int.from_bytes(gecko.peek_raw(session_adr + Offsets.SESSION_ID_IDX, length=0x1),
|
|
31
|
+
byteorder="big")
|
|
32
|
+
session_id = gecko.peek32(session_adr + session_id_idx * 4 + Offsets.SESSION_ID)
|
|
33
|
+
|
|
34
|
+
main_mgr_base_adr: int = gecko.peek32(Pointers.MAIN_MGR_BASE)
|
|
35
|
+
if main_mgr_base_adr != 0:
|
|
36
|
+
player_mgr_adr: int = gecko.peek32(main_mgr_base_adr + Offsets.PLAYER_MGR)
|
|
37
|
+
if player_mgr_adr != 0:
|
|
38
|
+
player_count = gecko.peek8(player_mgr_adr + Offsets.PLAYER_COUNT)
|
|
39
|
+
else:
|
|
40
|
+
print("You are currently not in a match. Logging previous match data "
|
|
41
|
+
"(Player 1 data will be your own, Session ID will be the current one).\n")
|
|
42
|
+
|
|
43
|
+
player_idx: int
|
|
44
|
+
for player_idx in range(player_count):
|
|
45
|
+
player_info_adr: int = gecko.peek32(player_info_ary_adr + player_idx * 0x4)
|
|
46
|
+
player_info_raw: bytes = gecko.peek_raw(player_info_adr, length=0xD4)
|
|
47
|
+
|
|
48
|
+
player_pid: int = int.from_bytes(player_info_raw[0xD0:0xD4], byteorder="big")
|
|
49
|
+
if player_pid != 0:
|
|
50
|
+
player_name: str = (player_info_raw[0x6:0x26]
|
|
51
|
+
.decode("utf-16-be")
|
|
52
|
+
.split("\u0000")[0]
|
|
53
|
+
.replace("\n", "")
|
|
54
|
+
.replace("\r", ""))
|
|
55
|
+
|
|
56
|
+
player_pnid: str = ""
|
|
57
|
+
player_mii_name: str = ""
|
|
58
|
+
request_error: str = ""
|
|
59
|
+
request_retry_count: int = 0
|
|
60
|
+
while request_retry_count < 3:
|
|
61
|
+
try:
|
|
62
|
+
response: requests.Response = requests.get(
|
|
63
|
+
f"https://account.pretendo.cc/v1/api/miis?pids={player_pid}",
|
|
64
|
+
headers={"X-Nintendo-Client-ID": "a2efa818a34fa16b8afbc8a74eba3eda",
|
|
65
|
+
"X-Nintendo-Client-Secret": "c91cdb5658bd4954ade78533a339cf9a"},
|
|
66
|
+
timeout=10
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
root: ElementTree.Element[str] = ElementTree.fromstring(response.text)
|
|
70
|
+
assert (isinstance(root[0].find("user_id"), ElementTree.Element) and
|
|
71
|
+
isinstance(root[0].find("name"), ElementTree.Element))
|
|
72
|
+
player_pnid = root[0].find("user_id").text # type: ignore
|
|
73
|
+
player_mii_name = (root[0].find("name") # type: ignore
|
|
74
|
+
.text
|
|
75
|
+
.replace("\n", "")
|
|
76
|
+
.replace("\r", ""))
|
|
77
|
+
request_fail = False
|
|
78
|
+
break
|
|
79
|
+
except (requests.HTTPError, AssertionError) as ex:
|
|
80
|
+
request_fail = True
|
|
81
|
+
request_error = str(ex)
|
|
82
|
+
request_retry_count += 1
|
|
83
|
+
time.sleep(1)
|
|
84
|
+
|
|
85
|
+
if request_fail and request_error not in request_errors:
|
|
86
|
+
request_errors.append(request_error)
|
|
87
|
+
|
|
88
|
+
if log_level != "none":
|
|
89
|
+
player_info: PlayerInfo = PlayerInfo(
|
|
90
|
+
index=player_idx,
|
|
91
|
+
pid=player_pid,
|
|
92
|
+
pnid=player_pnid,
|
|
93
|
+
name=player_name,
|
|
94
|
+
mii_name=player_mii_name,
|
|
95
|
+
region=int.from_bytes(player_info_raw[0x2C:0x30], byteorder="big"),
|
|
96
|
+
team=int.from_bytes(player_info_raw[0x33:0x34], byteorder="big"),
|
|
97
|
+
gender=int.from_bytes(player_info_raw[0x37:0x38], byteorder="big"),
|
|
98
|
+
skin_tone=int.from_bytes(player_info_raw[0x3B:0x3C], byteorder="big"),
|
|
99
|
+
eye_color=int.from_bytes(player_info_raw[0x3F:0x40], byteorder="big"),
|
|
100
|
+
weapon=int.from_bytes(player_info_raw[0x46:0x48], byteorder="big"),
|
|
101
|
+
sub_weapon=int.from_bytes(player_info_raw[0x4A:0x4C], byteorder="big"),
|
|
102
|
+
special_weapon=int.from_bytes(player_info_raw[0x4D:0x50], byteorder="big"),
|
|
103
|
+
shoes=int.from_bytes(player_info_raw[0x54:0x58], byteorder="big"),
|
|
104
|
+
clothes=int.from_bytes(player_info_raw[0x70:0x74], byteorder="big"),
|
|
105
|
+
headgear=int.from_bytes(player_info_raw[0x8C:0x90], byteorder="big"),
|
|
106
|
+
level=int.from_bytes(player_info_raw[0xAF:0xB0], byteorder="big", signed=True) + 1,
|
|
107
|
+
rank=int.from_bytes(player_info_raw[0xB3:0xB4], byteorder="big", signed=True)
|
|
108
|
+
)
|
|
109
|
+
player_info_list.append(player_info)
|
|
110
|
+
|
|
111
|
+
if not silent_logging:
|
|
112
|
+
print(f"Player {player_idx + 1} | "
|
|
113
|
+
f"PID: {player_pid:X} ({player_pid}) {' ' * 16 if player_pid == 0 else ''}| "
|
|
114
|
+
f"PNID: {player_pnid} {' ' * (16 - len(player_pnid))}| "
|
|
115
|
+
f"Name: {player_name}"
|
|
116
|
+
f"{' (Mii name: ' + player_mii_name + ')' if player_name != player_mii_name else ''}")
|
|
117
|
+
|
|
118
|
+
if not silent_logging:
|
|
119
|
+
print()
|
|
120
|
+
if request_fail:
|
|
121
|
+
print("Failed to get account data for one or more players: "
|
|
122
|
+
f"{'; '.join(request_errors)}\n")
|
|
123
|
+
|
|
124
|
+
if session_id != 0:
|
|
125
|
+
print(f"Session ID: {session_id:X} ({session_id})")
|
|
126
|
+
else:
|
|
127
|
+
print("Session ID: None")
|
|
128
|
+
|
|
129
|
+
print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
130
|
+
|
|
131
|
+
if log_level != "none":
|
|
132
|
+
match_logger.log_match(session_id, match_count, player_info_list)
|
|
133
|
+
|
|
134
|
+
# Main driver code.
|
|
135
|
+
epic_fail: bool = False
|
|
136
|
+
retry_count: int = 0
|
|
137
|
+
|
|
138
|
+
print(f"Splatlogger v{_VERSION} by Shadow Doggo\n")
|
|
139
|
+
|
|
140
|
+
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
|
141
|
+
description="A PID/PNID grabber and match logger for Splatoon using TCPGecko."
|
|
142
|
+
)
|
|
143
|
+
parser.add_argument("-ip", help="Your Wii U's LAN IP address.", required=True)
|
|
144
|
+
parser.add_argument("-log-level", help="Set how much data should be logged (see README).",
|
|
145
|
+
choices=["none", "basic", "full", "stats"], nargs="?", const="none", default="none")
|
|
146
|
+
parser.add_argument("-auto",
|
|
147
|
+
help="Enable auto logging (all to log every match, latest to only log the latest match).",
|
|
148
|
+
choices=["all", "latest"], nargs="?", const="all")
|
|
149
|
+
parser.add_argument("-aroma", help="Enable Aroma mode.", action="store_true")
|
|
150
|
+
parser.add_argument("-silent", help="Disable printing logs to the console.", action="store_true")
|
|
151
|
+
|
|
152
|
+
args: argparse.Namespace
|
|
153
|
+
if os.path.isfile(f"{userpaths.get_my_documents()}/Splatlogger/args.txt"):
|
|
154
|
+
with open(f"{userpaths.get_my_documents()}/Splatlogger/args.txt") as f:
|
|
155
|
+
args = parser.parse_args(f.read().split())
|
|
156
|
+
else:
|
|
157
|
+
args = parser.parse_args()
|
|
158
|
+
|
|
159
|
+
log_level: str = args.log_level
|
|
160
|
+
silent_logging: bool = args.silent
|
|
161
|
+
auto_logging: bool = True if args.auto in ("all", "latest") else False
|
|
162
|
+
log_latest: bool = True if args.auto == "latest" else False
|
|
163
|
+
aroma: bool = True if args.aroma else False
|
|
164
|
+
|
|
165
|
+
gecko: TCPGecko
|
|
166
|
+
try:
|
|
167
|
+
ipaddress.ip_address(args.ip)
|
|
168
|
+
print(f"Connecting to: {args.ip}")
|
|
169
|
+
gecko = TCPGeckoAroma(args.ip) if aroma else TCPGecko(args.ip)
|
|
170
|
+
except ValueError:
|
|
171
|
+
print("Please provide a valid IP address.\nExiting.")
|
|
172
|
+
sys.exit()
|
|
173
|
+
except OSError as e:
|
|
174
|
+
print(f"Failed to connect: {e}\nExiting.")
|
|
175
|
+
sys.exit()
|
|
176
|
+
|
|
177
|
+
static_mem_adr: int = 0
|
|
178
|
+
session_adr: int = 0
|
|
179
|
+
player_info_ary_adr: int = 0
|
|
180
|
+
while retry_count < 3:
|
|
181
|
+
try:
|
|
182
|
+
static_mem_adr = gecko.peek32(Pointers.STATIC_MEM)
|
|
183
|
+
session_adr = gecko.peek32(Pointers.SESSION)
|
|
184
|
+
player_info_ary_adr = gecko.peek32(static_mem_adr + Offsets.PLAYER_INFO_ARY)
|
|
185
|
+
|
|
186
|
+
epic_fail = False
|
|
187
|
+
break
|
|
188
|
+
except (OSError, TimeoutError, TCPGeckoException) as e:
|
|
189
|
+
print(f"\nAn error has occurred: {e}\nRetrying in 10 seconds...")
|
|
190
|
+
epic_fail = True
|
|
191
|
+
time.sleep(10)
|
|
192
|
+
|
|
193
|
+
if epic_fail:
|
|
194
|
+
print("\nFailed after 3 retries. Exiting.")
|
|
195
|
+
sys.exit()
|
|
196
|
+
|
|
197
|
+
print("Connected.\n")
|
|
198
|
+
retry_count = 0
|
|
199
|
+
|
|
200
|
+
match_logger: MatchLogger
|
|
201
|
+
if log_level != "none" and not auto_logging:
|
|
202
|
+
match_logger = MatchLogger(gecko, log_level, auto_logging, aroma, static_mem_adr)
|
|
203
|
+
try:
|
|
204
|
+
match_logger.create_new_log()
|
|
205
|
+
except OSError as e:
|
|
206
|
+
print(f"Failed to create log file: {e}\nExiting.")
|
|
207
|
+
sys.exit()
|
|
208
|
+
|
|
209
|
+
if log_level != "none" and auto_logging:
|
|
210
|
+
print("Auto logging enabled.")
|
|
211
|
+
|
|
212
|
+
match_logger = MatchLogger(gecko, log_level, auto_logging, aroma, static_mem_adr)
|
|
213
|
+
try:
|
|
214
|
+
match_logger.create_new_log()
|
|
215
|
+
except OSError as e:
|
|
216
|
+
print(f"Failed to create log file: {e}\nExiting.")
|
|
217
|
+
sys.exit()
|
|
218
|
+
|
|
219
|
+
match_in_progress: bool = False
|
|
220
|
+
count: int = 0
|
|
221
|
+
while True:
|
|
222
|
+
try:
|
|
223
|
+
main_mgr_vs_game_adr: int = gecko.peek32(Pointers.MAIN_MGR_VS_GAME)
|
|
224
|
+
if main_mgr_vs_game_adr == 0:
|
|
225
|
+
match_in_progress = False
|
|
226
|
+
|
|
227
|
+
if not match_in_progress and main_mgr_vs_game_adr != 0: # Trigger logging when a VS match is loaded.
|
|
228
|
+
match_in_progress = True
|
|
229
|
+
session_adr = gecko.peek32(Pointers.SESSION)
|
|
230
|
+
count += 1
|
|
231
|
+
|
|
232
|
+
if log_latest:
|
|
233
|
+
match_logger.create_new_log()
|
|
234
|
+
|
|
235
|
+
if not silent_logging:
|
|
236
|
+
print(f"\nMatch {count}\n")
|
|
237
|
+
|
|
238
|
+
time.sleep(1)
|
|
239
|
+
while retry_count < 3:
|
|
240
|
+
try:
|
|
241
|
+
splatlog(count)
|
|
242
|
+
epic_fail = False
|
|
243
|
+
break
|
|
244
|
+
except (OSError, TimeoutError, TCPGeckoException) as e:
|
|
245
|
+
print(f"\nAn error has occurred: {e}\nRetrying in 10 seconds...")
|
|
246
|
+
epic_fail = True
|
|
247
|
+
retry_count += 1
|
|
248
|
+
time.sleep(10)
|
|
249
|
+
|
|
250
|
+
if epic_fail:
|
|
251
|
+
print("\nFailed after 3 retries. Exiting.")
|
|
252
|
+
sys.exit()
|
|
253
|
+
|
|
254
|
+
retry_count = 0
|
|
255
|
+
time.sleep(10)
|
|
256
|
+
except KeyboardInterrupt:
|
|
257
|
+
print("\nExiting.")
|
|
258
|
+
break
|
|
259
|
+
except (OSError, TimeoutError, TCPGeckoException) as e:
|
|
260
|
+
if retry_count < 3:
|
|
261
|
+
print(f"\nAn error has occurred: {e}\nRetrying in 10 seconds...")
|
|
262
|
+
retry_count += 1
|
|
263
|
+
time.sleep(10)
|
|
264
|
+
else:
|
|
265
|
+
print("\nFailed after 3 retries. Exiting.")
|
|
266
|
+
break
|
|
267
|
+
|
|
268
|
+
if not auto_logging:
|
|
269
|
+
while retry_count < 3:
|
|
270
|
+
try:
|
|
271
|
+
splatlog(match_count=1)
|
|
272
|
+
epic_fail = False
|
|
273
|
+
break
|
|
274
|
+
except (OSError, TimeoutError, TCPGeckoException) as e:
|
|
275
|
+
print(f"\nAn error has occurred: {e}\nRetrying in 10 seconds...")
|
|
276
|
+
epic_fail = True
|
|
277
|
+
retry_count += 1
|
|
278
|
+
time.sleep(10)
|
|
279
|
+
|
|
280
|
+
if epic_fail:
|
|
281
|
+
print("\nFailed after 3 retries. Exiting.")
|
|
282
|
+
sys.exit()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == "__main__":
|
|
286
|
+
main()
|