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.
Files changed (24) hide show
  1. {splatlogger-1.6.3/src/Splatlogger.egg-info → splatlogger-1.6.4}/PKG-INFO +19 -14
  2. {splatlogger-1.6.3 → splatlogger-1.6.4}/README.md +16 -12
  3. {splatlogger-1.6.3 → splatlogger-1.6.4}/pyproject.toml +4 -3
  4. {splatlogger-1.6.3 → splatlogger-1.6.4/src/Splatlogger.egg-info}/PKG-INFO +19 -14
  5. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/SOURCES.txt +1 -0
  6. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/requires.txt +1 -0
  7. splatlogger-1.6.4/src/splatlogger/__main__.py +373 -0
  8. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/cemu_gecko.py +4 -2
  9. splatlogger-1.6.4/src/splatlogger/match_logger.py +249 -0
  10. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/tcpgecko.py +33 -31
  11. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/tcpgecko_aroma.py +10 -10
  12. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/tuples.py +5 -2
  13. splatlogger-1.6.4/src/splatlogger/utility_functions.py +173 -0
  14. splatlogger-1.6.3/src/splatlogger/__main__.py +0 -404
  15. splatlogger-1.6.3/src/splatlogger/match_logger.py +0 -210
  16. {splatlogger-1.6.3 → splatlogger-1.6.4}/MANIFEST.in +0 -0
  17. {splatlogger-1.6.3 → splatlogger-1.6.4}/setup.cfg +0 -0
  18. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/dependency_links.txt +0 -0
  19. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/entry_points.txt +0 -0
  20. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/Splatlogger.egg-info/top_level.txt +0 -0
  21. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/__init__.py +0 -0
  22. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/data/api.json +0 -0
  23. {splatlogger-1.6.3 → splatlogger-1.6.4}/src/splatlogger/data/names.json +0 -0
  24. {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.3
4
- Summary: PID (Principal ID)/PNID grabber and match logger for Splatoon
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)/PNID grabber and match logger for Splatoon (Wii U), written in Python.
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
- Your Wii U will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew,
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
- To install the package from PyPI, run:
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 the module directly from the source code.
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 the module:
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 module from source in that case.
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)/PNID grabber and match logger for Splatoon (Wii U), written in Python.
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
- Your Wii U will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew,
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
- To install the package from PyPI, run:
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 the module directly from the source code.
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 the module:
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 module from source in that case.
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.3"
7
+ version = "1.6.4"
8
8
  authors = [
9
9
  { name="Shadow Doggo", email="shadowdoggo@protonmail.com" }
10
10
  ]
11
- description = "PID (Principal ID)/PNID grabber and match logger for Splatoon"
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.3
4
- Summary: PID (Principal ID)/PNID grabber and match logger for Splatoon
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)/PNID grabber and match logger for Splatoon (Wii U), written in Python.
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
- Your Wii U will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew,
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
- To install the package from PyPI, run:
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 the module directly from the source code.
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 the module:
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 module from source in that case.
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
@@ -15,5 +15,6 @@ src/splatlogger/match_logger.py
15
15
  src/splatlogger/tcpgecko.py
16
16
  src/splatlogger/tcpgecko_aroma.py
17
17
  src/splatlogger/tuples.py
18
+ src/splatlogger/utility_functions.py
18
19
  src/splatlogger/data/api.json
19
20
  src/splatlogger/data/names.json
@@ -2,3 +2,4 @@ userpaths
2
2
  requests
3
3
  psutil
4
4
  pymemoryeditor
5
+ colorama
@@ -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 = -1):
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(Exception): pass
88
+ class CemuGeckoError(TCPGeckoError): pass