Splatlogger 1.5.3__tar.gz → 1.6__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.
@@ -1,27 +1,35 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Splatlogger
3
- Version: 1.5.3
4
- Summary: A PID/PNID grabber and match logger for Splatoon using TCPGecko.
3
+ Version: 1.6
4
+ Summary: A CLI PID (Principal ID)/PNID grabber and match logger for Splatoon.
5
5
  Author-email: Shadow Doggo <shadowdoggo@protonmail.com>
6
6
  License-Expression: MIT
7
- Project-URL: Source Code, https://github.com/spoongaming61/Splatlogger
7
+ Project-URL: Source Code, https://codeberg.org/ShadowDoggo/Splatlogger
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.11
11
11
  Description-Content-Type: text/markdown
12
12
  Requires-Dist: userpaths
13
13
  Requires-Dist: requests
14
+ Requires-Dist: psutil
15
+ Requires-Dist: pymemoryeditor
14
16
 
15
17
  # Splatlogger
16
- A PID/PNID grabber and match logger for Splatoon using TCPGecko, written in Python.
18
+ A CLI PID (Principal ID)/PNID grabber and match logger for Splatoon, written in Python.
17
19
 
18
20
  ## Prerequisites
19
- You'll need a modded Wii U running either the Tiramisu or Aroma environment, as well as Python 3.11 or newer installed on your system
20
- (only required if you're installing the package, on Android devices you can use Termux).
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.
24
+
25
+ Both the Wii U and Cemu (Windows and Linux only) are supported.
26
+
27
+ Your Wii U will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew
28
+ (e.g. Haxchi, browser exploit).
21
29
 
22
30
  On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko) (Geckiine or SDGeckiine will work as well).
23
31
 
24
- On Aroma, install the [TCPGecko Aroma plugin](https://github.com/spoongaming61/TCPGeckoAroma).
32
+ On Aroma, install the [TCPGecko Aroma plugin](https://codeberg.org/ShadowDoggo/TCPGeckoAroma).
25
33
 
26
34
  ## Installation
27
35
  To install the package from PyPI, run:
@@ -34,7 +42,8 @@ Or download the latest release from GitHub and run:
34
42
  python -m pip install /path/to/Splatlogger-1.x.zip
35
43
  ```
36
44
 
37
- On Windows, you can skip the installation and instead use the executable provided on the releases page. Though if you already have Python installed, it's recommended to use the package.
45
+ On Windows, use the executable provided on the releases page.
46
+ Though if you already have Python installed, it's recommended to use the package.
38
47
 
39
48
  If you do have Python installed but do not wish to install the package, you can run the module directly from the source code:
40
49
  ```
@@ -67,11 +76,20 @@ Options:
67
76
 
68
77
  - `-aroma` - Switch to Aroma mode. Enable if using the TCPGecko Aroma plugin.
69
78
 
79
+ - `-cemu [PID]` - Switch to Cemu mode. The `-ip` argument is not required and will be ignored.
80
+ - `PID` - PID of the Cemu process (optional).
81
+
82
+ - `-stack Address (in hex)` - (Cemu only) Beginning address of the stack space for Default Core 1 (Debug > View PPC threads).
83
+ Reuired for logging stats, not needed otherwise.
84
+
85
+ <img src="stack.png" width="600" alt="(example)">
86
+
70
87
  - `-silent` - Disable printing logs to the console.
71
88
 
72
89
  Logs are saved in `/[User]/Documents/Splatlogger/logs/[Date]`.
73
90
 
74
- To always run with the same IP and options without having to type them in manually, create an `args.txt` file in `/[User]/Documents/Splatlogger` and put the arguments following `splatlogger` in there.
91
+ To always run with the same IP and options without having to type them in manually, create an `args.txt` file in
92
+ `/[User]/Documents/Splatlogger` and put the arguments following `splatlogger` in there.
75
93
  Afterward any arguments passed through the command line will be ignored.
76
94
 
77
95
  Only one program can be connected to TCPGecko at a time. If you have something else connected, disconnect it beforehand.
@@ -1,27 +1,19 @@
1
- Metadata-Version: 2.4
2
- Name: Splatlogger
3
- Version: 1.5.3
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
- Requires-Dist: userpaths
13
- Requires-Dist: requests
14
-
15
1
  # Splatlogger
16
- A PID/PNID grabber and match logger for Splatoon using TCPGecko, written in Python.
2
+ A CLI PID (Principal ID)/PNID grabber and match logger for Splatoon, written in Python.
17
3
 
18
4
  ## Prerequisites
19
- You'll need a modded Wii U running either the Tiramisu or Aroma environment, as well as Python 3.11 or newer installed on your system
20
- (only required if you're installing the package, on Android devices you can use Termux).
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.
8
+
9
+ Both the Wii U and Cemu (Windows and Linux only) are supported.
10
+
11
+ Your Wii U will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew
12
+ (e.g. Haxchi, browser exploit).
21
13
 
22
14
  On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko) (Geckiine or SDGeckiine will work as well).
23
15
 
24
- On Aroma, install the [TCPGecko Aroma plugin](https://github.com/spoongaming61/TCPGeckoAroma).
16
+ On Aroma, install the [TCPGecko Aroma plugin](https://codeberg.org/ShadowDoggo/TCPGeckoAroma).
25
17
 
26
18
  ## Installation
27
19
  To install the package from PyPI, run:
@@ -34,7 +26,8 @@ Or download the latest release from GitHub and run:
34
26
  python -m pip install /path/to/Splatlogger-1.x.zip
35
27
  ```
36
28
 
37
- On Windows, you can skip the installation and instead use the executable provided on the releases page. Though if you already have Python installed, it's recommended to use the package.
29
+ On Windows, use the executable provided on the releases page.
30
+ Though if you already have Python installed, it's recommended to use the package.
38
31
 
39
32
  If you do have Python installed but do not wish to install the package, you can run the module directly from the source code:
40
33
  ```
@@ -67,11 +60,20 @@ Options:
67
60
 
68
61
  - `-aroma` - Switch to Aroma mode. Enable if using the TCPGecko Aroma plugin.
69
62
 
63
+ - `-cemu [PID]` - Switch to Cemu mode. The `-ip` argument is not required and will be ignored.
64
+ - `PID` - PID of the Cemu process (optional).
65
+
66
+ - `-stack Address (in hex)` - (Cemu only) Beginning address of the stack space for Default Core 1 (Debug > View PPC threads).
67
+ Reuired for logging stats, not needed otherwise.
68
+
69
+ <img src="stack.png" width="600" alt="(example)">
70
+
70
71
  - `-silent` - Disable printing logs to the console.
71
72
 
72
73
  Logs are saved in `/[User]/Documents/Splatlogger/logs/[Date]`.
73
74
 
74
- To always run with the same IP and options without having to type them in manually, create an `args.txt` file in `/[User]/Documents/Splatlogger` and put the arguments following `splatlogger` in there.
75
+ To always run with the same IP and options without having to type them in manually, create an `args.txt` file in
76
+ `/[User]/Documents/Splatlogger` and put the arguments following `splatlogger` in there.
75
77
  Afterward any arguments passed through the command line will be ignored.
76
78
 
77
79
  Only one program can be connected to TCPGecko at a time. If you have something else connected, disconnect it beforehand.
@@ -4,16 +4,18 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "Splatlogger"
7
- version = "1.5.3"
7
+ version = "1.6"
8
8
  authors = [
9
9
  { name="Shadow Doggo", email="shadowdoggo@protonmail.com" }
10
10
  ]
11
- description = "A PID/PNID grabber and match logger for Splatoon using TCPGecko."
11
+ description = "A CLI PID (Principal ID)/PNID grabber and match logger for Splatoon."
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.11"
14
14
  dependencies = [
15
15
  "userpaths",
16
- "requests"
16
+ "requests",
17
+ "psutil",
18
+ "pymemoryeditor"
17
19
  ]
18
20
  classifiers = [
19
21
  "Programming Language :: Python :: 3",
@@ -23,7 +25,7 @@ license = "MIT"
23
25
  license-files = ["LICENSE"]
24
26
 
25
27
  [project.urls]
26
- "Source Code" = "https://github.com/spoongaming61/Splatlogger"
28
+ "Source Code" = "https://codeberg.org/ShadowDoggo/Splatlogger"
27
29
 
28
30
  [project.scripts]
29
31
  splatlogger = "splatlogger.__main__:main"
@@ -1,13 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: Splatlogger
3
+ Version: 1.6
4
+ Summary: A CLI PID (Principal ID)/PNID grabber and match logger for Splatoon.
5
+ Author-email: Shadow Doggo <shadowdoggo@protonmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Source Code, https://codeberg.org/ShadowDoggo/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
+ Requires-Dist: userpaths
13
+ Requires-Dist: requests
14
+ Requires-Dist: psutil
15
+ Requires-Dist: pymemoryeditor
16
+
1
17
  # Splatlogger
2
- A PID/PNID grabber and match logger for Splatoon using TCPGecko, written in Python.
18
+ A CLI PID (Principal ID)/PNID grabber and match logger for Splatoon, written in Python.
3
19
 
4
20
  ## Prerequisites
5
- You'll need a modded Wii U running either the Tiramisu or Aroma environment, as well as Python 3.11 or newer installed on your system
6
- (only required if you're installing the package, on Android devices you can use Termux).
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.
24
+
25
+ Both the Wii U and Cemu (Windows and Linux only) are supported.
26
+
27
+ Your Wii U will need to have either the Tiramisu or Aroma environment set up or have another way to run homebrew
28
+ (e.g. Haxchi, browser exploit).
7
29
 
8
30
  On Tiramisu, use [TCPGecko](https://github.com/BullyWiiPlaza/tcpgecko) (Geckiine or SDGeckiine will work as well).
9
31
 
10
- On Aroma, install the [TCPGecko Aroma plugin](https://github.com/spoongaming61/TCPGeckoAroma).
32
+ On Aroma, install the [TCPGecko Aroma plugin](https://codeberg.org/ShadowDoggo/TCPGeckoAroma).
11
33
 
12
34
  ## Installation
13
35
  To install the package from PyPI, run:
@@ -20,7 +42,8 @@ Or download the latest release from GitHub and run:
20
42
  python -m pip install /path/to/Splatlogger-1.x.zip
21
43
  ```
22
44
 
23
- On Windows, you can skip the installation and instead use the executable provided on the releases page. Though if you already have Python installed, it's recommended to use the package.
45
+ On Windows, use the executable provided on the releases page.
46
+ Though if you already have Python installed, it's recommended to use the package.
24
47
 
25
48
  If you do have Python installed but do not wish to install the package, you can run the module directly from the source code:
26
49
  ```
@@ -53,11 +76,20 @@ Options:
53
76
 
54
77
  - `-aroma` - Switch to Aroma mode. Enable if using the TCPGecko Aroma plugin.
55
78
 
79
+ - `-cemu [PID]` - Switch to Cemu mode. The `-ip` argument is not required and will be ignored.
80
+ - `PID` - PID of the Cemu process (optional).
81
+
82
+ - `-stack Address (in hex)` - (Cemu only) Beginning address of the stack space for Default Core 1 (Debug > View PPC threads).
83
+ Reuired for logging stats, not needed otherwise.
84
+
85
+ <img src="stack.png" width="600" alt="(example)">
86
+
56
87
  - `-silent` - Disable printing logs to the console.
57
88
 
58
89
  Logs are saved in `/[User]/Documents/Splatlogger/logs/[Date]`.
59
90
 
60
- To always run with the same IP and options without having to type them in manually, create an `args.txt` file in `/[User]/Documents/Splatlogger` and put the arguments following `splatlogger` in there.
91
+ To always run with the same IP and options without having to type them in manually, create an `args.txt` file in
92
+ `/[User]/Documents/Splatlogger` and put the arguments following `splatlogger` in there.
61
93
  Afterward any arguments passed through the command line will be ignored.
62
94
 
63
95
  Only one program can be connected to TCPGecko at a time. If you have something else connected, disconnect it beforehand.
@@ -8,6 +8,7 @@ src/Splatlogger.egg-info/requires.txt
8
8
  src/Splatlogger.egg-info/top_level.txt
9
9
  src/splatlogger/__init__.py
10
10
  src/splatlogger/__main__.py
11
+ src/splatlogger/cemu_gecko.py
11
12
  src/splatlogger/data.py
12
13
  src/splatlogger/match_logger.py
13
14
  src/splatlogger/tcpgecko.py
@@ -0,0 +1,4 @@
1
+ userpaths
2
+ requests
3
+ psutil
4
+ pymemoryeditor
@@ -5,19 +5,20 @@ import ipaddress
5
5
  import os
6
6
  import sys
7
7
  import time
8
- import xml.etree.ElementTree as ElementTree
9
8
  from datetime import datetime
10
9
  from typing import NamedTuple
10
+ from xml.etree import ElementTree
11
11
 
12
12
  import requests
13
13
  import userpaths # type: ignore[import-untyped]
14
14
 
15
+ from .cemu_gecko import CemuGecko, CemuGeckoException
15
16
  from .data import Pointers, Offsets, PlayerInfo
16
17
  from .match_logger import MatchLogger
17
18
  from .tcpgecko import TCPGecko, TCPGeckoException
18
19
  from .tcpgecko_aroma import TCPGeckoAroma
19
20
 
20
- _VERSION: str = "1.5.3"
21
+ _VERSION = "1.6"
21
22
 
22
23
 
23
24
  class Options(NamedTuple):
@@ -26,6 +27,8 @@ class Options(NamedTuple):
26
27
  log_latest: bool
27
28
  silent_logging: bool
28
29
  aroma: bool
30
+ cemu: bool
31
+ stack: int | None
29
32
 
30
33
 
31
34
  class AccountInfo(NamedTuple):
@@ -37,70 +40,106 @@ class AccountInfo(NamedTuple):
37
40
  def main() -> None:
38
41
  print(f"Splatlogger v{_VERSION} by Shadow Doggo\n")
39
42
 
40
- parser: argparse.ArgumentParser = argparse.ArgumentParser(
41
- description="A PID/PNID grabber and match logger for Splatoon using TCPGecko."
42
- )
43
- parser.add_argument("-ip", help="Your Wii U's LAN IP address.", required=True)
43
+ parser = argparse.ArgumentParser(description="A CLI PID (Principal ID)/PNID grabber and match logger for Splatoon.")
44
+ parser.add_argument("-ip", help="Your Wii U's LAN IP address.")
44
45
  parser.add_argument("-log-level", help="Set how much data should be logged (see README).",
45
- choices=["none", "standard", "extended", "stats"],
46
- nargs="?", const="standard", default="standard")
46
+ choices=["none", "standard", "extended", "stats"], default="standard")
47
47
  parser.add_argument("-auto",
48
48
  help="Enable auto logging (see README).",
49
49
  choices=["all", "latest"], nargs="?", const="all")
50
50
  parser.add_argument("-aroma", help="Switch to Aroma mode. Enable if using the TCPGecko Aroma plugin.",
51
51
  action="store_true")
52
+ parser.add_argument("-cemu",
53
+ help="Switch to Cemu mode (optional: PID of the Cemu process).", metavar="PID",
54
+ nargs="?", const=-1)
55
+ parser.add_argument("-stack",
56
+ help="Beginning address of the stack space for Default Core 1 (Cemu only, see README).",
57
+ metavar="Address (in hex)")
52
58
  parser.add_argument("-silent", help="Disable printing logs to the console.", action="store_true")
53
59
 
54
- args: argparse.Namespace
55
60
  if os.path.isfile(f"{userpaths.get_my_documents()}/Splatlogger/args.txt"):
56
61
  with open(f"{userpaths.get_my_documents()}/Splatlogger/args.txt", encoding="utf-8") as f:
57
62
  args = parser.parse_args(f.read().split())
58
63
  else:
59
64
  args = parser.parse_args()
60
65
 
61
- options: Options = Options(log_level=args.log_level,
62
- auto_logging=args.auto in ("all", "latest"),
63
- log_latest=args.auto == "latest",
64
- silent_logging=args.silent,
65
- aroma=args.aroma)
66
-
67
- gecko: TCPGecko
68
- try:
69
- ipaddress.ip_address(args.ip)
70
- print(f"Connecting to: {args.ip}")
71
- gecko = TCPGeckoAroma(args.ip) if options.aroma else TCPGecko(args.ip)
72
- except ValueError:
73
- print("Please provide a valid IP address.\nExiting.")
74
- sys.exit()
75
- except OSError as e:
76
- print(f"Failed to connect: {e}\nExiting.")
66
+ if args.aroma and args.cemu:
67
+ print("Cannot use both Aroma and Cemu modes.")
77
68
  sys.exit()
78
69
 
79
- static_mem_adr: int = 0
80
- player_info_ary_adr: int = 0
70
+ stack_addr: int | None = None
71
+ if args.stack:
72
+ try:
73
+ stack_addr = int(args.stack, 16)
74
+ except ValueError:
75
+ print("Invalid stack address. Please provide a valid hex value.")
76
+ sys.exit()
77
+
78
+ options = Options(log_level=args.log_level,
79
+ auto_logging=args.auto in ("all", "latest"),
80
+ log_latest=args.auto == "latest",
81
+ silent_logging=args.silent,
82
+ aroma=args.aroma,
83
+ cemu=args.cemu is not None,
84
+ stack=stack_addr)
85
+
86
+ ptr_offset = 0
87
+ gecko: TCPGecko | TCPGeckoAroma | CemuGecko
88
+ if args.cemu:
89
+ ptr_offset = 0x503000
90
+ if args.log_level == "stats" and not args.stack:
91
+ print("Stack space address must be specified for stats logging on Cemu (see README).")
92
+ sys.exit()
93
+ try:
94
+ print("Opening Cemu process...")
95
+ gecko = CemuGecko(int(args.cemu))
96
+ except CemuGeckoException as e:
97
+ print(e)
98
+ sys.exit()
99
+ else:
100
+ if not args.ip:
101
+ print('No IP address was provided. Run "splatlogger -h" for help.')
102
+ sys.exit()
103
+ try:
104
+ ipaddress.ip_address(args.ip)
105
+ print(f"Connecting to: {args.ip}")
106
+ gecko = TCPGeckoAroma(args.ip) if options.aroma else TCPGecko(args.ip)
107
+ except ValueError:
108
+ print("Please provide a valid IP address.")
109
+ sys.exit()
110
+ except TCPGeckoException as e:
111
+ print(e)
112
+ sys.exit()
113
+
81
114
  for _ in range(3):
82
115
  try:
83
- static_mem_adr = gecko.peek32(Pointers.STATIC_MEM)
84
- player_info_ary_adr = gecko.peek32(static_mem_adr + Offsets.PLAYER_INFO_ARY)
85
- print("Connected.\n")
116
+ static_mem_adr = gecko.peek32(Pointers.STATIC_MEM - ptr_offset)
117
+ player_info_ary_adr = gecko.peek32(static_mem_adr + Offsets.STATICMEM_PLAYER_INFO_ARY)
118
+ assert static_mem_adr != 0 and player_info_ary_adr != 0
119
+
120
+ print("Success.\n" if options.cemu else "Connected.\n")
86
121
  break
87
122
  except (OSError, TimeoutError, TCPGeckoException) as e:
88
123
  print(f"\nAn error has occurred: {e}\nRetrying in 10 seconds...")
89
124
  time.sleep(10)
125
+ except AssertionError:
126
+ print("\nUnable to find StaticMem and PlayerInfoAry address.\nRetrying in 10 seconds...")
127
+ time.sleep(10)
90
128
  else:
91
- print("\nFailed after 3 tries.\nExiting.")
129
+ print("\nFailed after 3 tries.")
92
130
  sys.exit()
93
131
 
94
132
  match_logger: MatchLogger | None = None
95
133
  if options.log_level != "none":
96
- match_logger = MatchLogger(gecko, options.log_level, options.auto_logging, options.aroma, static_mem_adr)
134
+ match_logger = MatchLogger(gecko, options.log_level, options.auto_logging, options.aroma,
135
+ options.cemu, options.stack, static_mem_adr)
97
136
  try:
98
137
  match_logger.create_new_log()
99
138
  except OSError as e:
100
- print(f"Failed to create log file: {e}\nExiting.")
139
+ print(f"Failed to create log file: {e}")
101
140
  sys.exit()
102
141
 
103
- if options.auto_logging and match_logger is not None:
142
+ if options.auto_logging and match_logger:
104
143
  print("Auto logging enabled.")
105
144
  _auto_log(options, gecko, match_logger, player_info_ary_adr)
106
145
 
@@ -112,18 +151,19 @@ def main() -> None:
112
151
  print(f"\nAn error has occurred: {e}\nRetrying in 10 seconds...")
113
152
  time.sleep(10)
114
153
  else:
115
- print("\nFailed after 3 tries.\nExiting.")
154
+ print("\nFailed after 3 tries.")
116
155
  sys.exit()
117
156
 
118
157
 
119
- def _auto_log(options: Options, gecko: TCPGecko, match_logger: MatchLogger,
158
+ def _auto_log(options: Options, gecko: TCPGecko | CemuGecko, match_logger: MatchLogger,
120
159
  player_info_ary_adr: int) -> None:
121
- match_in_progress: bool = False
122
- count: int = 0
123
- retry_count: int = 0
160
+ match_in_progress = False
161
+ count = 0
162
+ retry_count = 0
163
+ ptr_offset = 0x503000 if options.cemu else 0
124
164
  while True:
125
165
  try:
126
- main_mgr_vs_game_adr: int = gecko.peek32(Pointers.MAIN_MGR_VS_GAME)
166
+ main_mgr_vs_game_adr = gecko.peek32(Pointers.MAIN_MGR_VS_GAME - ptr_offset)
127
167
  if main_mgr_vs_game_adr == 0:
128
168
  match_in_progress = False
129
169
 
@@ -138,6 +178,7 @@ def _auto_log(options: Options, gecko: TCPGecko, match_logger: MatchLogger,
138
178
  print(f"\nMatch {count}\n")
139
179
 
140
180
  time.sleep(1)
181
+
141
182
  for _ in range(3):
142
183
  try:
143
184
  _splatlog(options, gecko, match_logger, player_info_ary_adr, count)
@@ -146,7 +187,7 @@ def _auto_log(options: Options, gecko: TCPGecko, match_logger: MatchLogger,
146
187
  print(f"\nAn error has occurred: {e}\nRetrying in 10 seconds...")
147
188
  time.sleep(10)
148
189
  else:
149
- print("\nFailed after 3 tries.\nExiting.")
190
+ print("\nFailed after 3 tries.")
150
191
  sys.exit()
151
192
 
152
193
  retry_count = 0
@@ -160,61 +201,60 @@ def _auto_log(options: Options, gecko: TCPGecko, match_logger: MatchLogger,
160
201
  retry_count += 1
161
202
  time.sleep(10)
162
203
  else:
163
- print("\nFailed after 3 tries.\nExiting.")
204
+ print("\nFailed after 3 tries.")
164
205
  sys.exit()
165
206
 
166
207
 
167
- def _splatlog(options: Options, gecko: TCPGecko, match_logger: MatchLogger | None,
208
+ def _splatlog(options: Options, gecko: TCPGecko | CemuGecko, match_logger: MatchLogger | None,
168
209
  player_info_ary_adr: int, match_count: int) -> None:
169
- session_id: int = 0
170
- player_count: int = 8
210
+ session_id = 0
211
+ player_count = 8
171
212
  request_errors: list[str] = []
172
213
  player_info_list: list[PlayerInfo] = []
173
- time_: datetime = datetime.now()
214
+ date = datetime.now()
215
+ ptr_offset = 0x503000 if options.cemu else 0
174
216
 
175
- session_adr = gecko.peek32(Pointers.SESSION)
217
+ session_adr = gecko.peek32(Pointers.SESSION - ptr_offset)
176
218
  if session_adr != 0:
177
- session_id_idx: int = int.from_bytes(gecko.peek_raw(session_adr + Offsets.SESSION_ID_IDX,
178
- length=0x1), byteorder="big")
219
+ session_id_idx = int.from_bytes(gecko.peek_raw(session_adr + Offsets.SESSION_ID_IDX,
220
+ length=1), byteorder="big")
179
221
  session_id = gecko.peek32(session_adr + session_id_idx * 4 + Offsets.SESSION_ID)
180
222
 
181
- main_mgr_base_adr: int = gecko.peek32(Pointers.MAIN_MGR_BASE)
182
- if main_mgr_base_adr != 0 and gecko.peek8(Pointers.MAIN_MGR_VS_GAME) != 0:
183
- player_mgr_adr: int = gecko.peek32(main_mgr_base_adr + Offsets.PLAYER_MGR)
223
+ main_mgr_base_adr = gecko.peek32(Pointers.MAIN_MGR_BASE - ptr_offset)
224
+ if main_mgr_base_adr != 0 and gecko.peek8(Pointers.MAIN_MGR_VS_GAME - ptr_offset) != 0:
225
+ player_mgr_adr = gecko.peek32(main_mgr_base_adr + Offsets.MAINMGR_PLAYER_MGR)
184
226
  if player_mgr_adr != 0:
185
- player_count = gecko.peek8(player_mgr_adr + Offsets.PLAYER_COUNT)
227
+ player_count = gecko.peek8(player_mgr_adr + Offsets.PLAYERMGR_PLAYER_COUNT)
186
228
  else:
187
229
  print("You are currently not in a match. Logging previous match data "
188
230
  "(Player 1 data will be your own, Session ID will be the current one).\n")
189
231
 
190
- print(f"{'Player':8} | {'PID (Hex)':10} | {'PID (Dec)':10} | {'PNID':16} | Name\n"
232
+ print(f"{'Player':<8} | {'PID (Hex)':<10} | {'PID (Dec)':<10} | {'PNID':<16} | Name\n"
191
233
  f"{'-' * 73}")
192
234
 
193
- player_idx: int
194
235
  for player_idx in range(player_count):
195
- player_info_raw: bytes = gecko.peek_raw(gecko.peek32(player_info_ary_adr + player_idx * 0x4),
196
- length=0xD4)
236
+ player_info_raw = gecko.peek_raw(gecko.peek32(player_info_ary_adr + player_idx * 0x4),
237
+ length=0xD4)
197
238
 
198
- player_pid: int = int.from_bytes(player_info_raw[0xD0:0xD4], byteorder="big")
239
+ player_pid = int.from_bytes(player_info_raw[0xD0:0xD4], byteorder="big")
199
240
  if player_pid != 0:
200
- player_name: str = (player_info_raw[0x6:0x26]
201
- .decode("utf-16-be")
202
- .split("\u0000")[0]
203
- .replace("\n", "")
204
- .replace("\r", ""))
241
+ player_name = (player_info_raw[0x6:0x26]
242
+ .decode("utf-16-be")
243
+ .split("\u0000")[0]
244
+ .replace("\n", "")
245
+ .replace("\r", ""))
205
246
 
206
- act_info: AccountInfo = _get_act_info(player_pid)
247
+ act_info = _get_act_info(player_pid)
207
248
  if act_info.request_error != "":
208
249
  request_errors.append(act_info.request_error)
209
250
 
210
251
  if options.log_level != "none":
211
- player_info: PlayerInfo = PlayerInfo(
252
+ player_info = PlayerInfo(
212
253
  idx=player_idx,
213
254
  pid=player_pid,
214
255
  pnid=act_info.pnid,
215
256
  name=player_name,
216
257
  mii_name=act_info.mii_name,
217
- region=int.from_bytes(player_info_raw[0x2C:0x30], byteorder="big"),
218
258
  team=int.from_bytes(player_info_raw[0x33:0x34], byteorder="big"),
219
259
  gender=int.from_bytes(player_info_raw[0x37:0x38], byteorder="big"),
220
260
  skin_tone=int.from_bytes(player_info_raw[0x3B:0x3C], byteorder="big"),
@@ -232,9 +272,9 @@ def _splatlog(options: Options, gecko: TCPGecko, match_logger: MatchLogger | Non
232
272
 
233
273
  if not options.silent_logging:
234
274
  print(f"Player {player_idx + 1} | "
235
- f"0x{f'{player_pid:X}':8} | "
275
+ f"0x{f'{player_pid:X}':<8} | "
236
276
  f"{player_pid:<10} | "
237
- f"{act_info.pnid:16} | "
277
+ f"{act_info.pnid:<16} | "
238
278
  f"{player_name} {f'({act_info.mii_name})' if player_name != act_info.mii_name else ''}")
239
279
 
240
280
  if not options.silent_logging:
@@ -248,29 +288,35 @@ def _splatlog(options: Options, gecko: TCPGecko, match_logger: MatchLogger | Non
248
288
  else:
249
289
  print("Session ID: None")
250
290
 
251
- print(f"Fetched at: {time_.strftime('%Y-%m-%d %H:%M:%S')}")
291
+ print(f"Fetched at: {date.strftime('%Y-%m-%d %H:%M:%S')}")
252
292
 
253
- if options.log_level != "none" and match_logger is not None:
254
- match_logger.log_match(time_, session_id, match_count, player_info_list)
293
+ if options.log_level != "none" and match_logger:
294
+ match_logger.log_match(date, session_id, match_count, player_info_list)
255
295
 
256
296
 
257
297
  def _get_act_info(player_pid: int) -> AccountInfo:
258
- pnid: str = ""
259
- mii_name: str = ""
260
- request_error: str = ""
298
+ pnid = ""
299
+ mii_name = ""
300
+ request_error = ""
261
301
 
262
302
  for _ in range(3):
263
303
  try:
264
- response: requests.Response = requests.get(
304
+ response = requests.get(
265
305
  f"https://account.pretendo.cc/v1/api/miis?pids={player_pid}",
266
306
  headers={"X-Nintendo-Client-ID": "a2efa818a34fa16b8afbc8a74eba3eda",
267
307
  "X-Nintendo-Client-Secret": "c91cdb5658bd4954ade78533a339cf9a"},
268
- timeout=10
308
+ timeout=5
269
309
  )
270
310
 
271
- root: ElementTree.Element[str] = ElementTree.fromstring(response.text)
272
- assert (isinstance(root[0].find("user_id"), ElementTree.Element) and
273
- isinstance(root[0].find("name"), ElementTree.Element))
311
+ try:
312
+ root = ElementTree.fromstring(response.text)
313
+ except ElementTree.ParseError:
314
+ return AccountInfo("", "", "Got empty or invalid response.")
315
+
316
+ if not (isinstance(root[0].find("user_id"), ElementTree.Element) and
317
+ isinstance(root[0].find("name"), ElementTree.Element)):
318
+ return AccountInfo("", "", "Got empty or invalid response.")
319
+
274
320
  pnid = root[0].find("user_id").text # type: ignore
275
321
  mii_name = (root[0].find("name") # type: ignore
276
322
  .text