sssekai 0.8.1__py3-none-any.whl
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.
- sssekai/__gui__.py +72 -0
- sssekai/__init__.py +6 -0
- sssekai/__main__.py +547 -0
- sssekai/abcache/__init__.py +771 -0
- sssekai/abcache/auth.py +30 -0
- sssekai/abcache/fs.py +275 -0
- sssekai/crypto/APIManager.py +42 -0
- sssekai/crypto/AssetBundle.py +26 -0
- sssekai/crypto/__init__.py +0 -0
- sssekai/entrypoint/__init__.py +0 -0
- sssekai/entrypoint/abcache.py +299 -0
- sssekai/entrypoint/abdecrypt.py +26 -0
- sssekai/entrypoint/abserve.py +123 -0
- sssekai/entrypoint/apidecrypt.py +20 -0
- sssekai/entrypoint/apphash.py +276 -0
- sssekai/entrypoint/il2cpp.py +45 -0
- sssekai/entrypoint/il2cppextract.py +52 -0
- sssekai/entrypoint/live2dextract.py +74 -0
- sssekai/entrypoint/moc3paths.py +38 -0
- sssekai/entrypoint/mvdata.py +34 -0
- sssekai/entrypoint/rla2json.py +79 -0
- sssekai/entrypoint/spineextract.py +64 -0
- sssekai/entrypoint/usmdemux.py +29 -0
- sssekai/fmt/__init__.py +0 -0
- sssekai/fmt/moc3.py +47 -0
- sssekai/fmt/motion3.py +92 -0
- sssekai/fmt/rla.py +516 -0
- sssekai/generated/Sekai/__init__.py +66 -0
- sssekai/generated/__init__.py +96 -0
- sssekai/unity/AnimationClip.py +668 -0
- sssekai/unity/AssetBundle.py +13 -0
- sssekai/unity/__init__.py +19 -0
- sssekai/unity/constant/SekaiLive2DPathNames.py +1397 -0
- sssekai/unity/constant/__init__.py +0 -0
- sssekai-0.8.1.dist-info/METADATA +86 -0
- sssekai-0.8.1.dist-info/RECORD +40 -0
- sssekai-0.8.1.dist-info/WHEEL +5 -0
- sssekai-0.8.1.dist-info/entry_points.txt +6 -0
- sssekai-0.8.1.dist-info/licenses/LICENSE +21 -0
- sssekai-0.8.1.dist-info/top_level.txt +1 -0
sssekai/__gui__.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from sssekai.__main__ import create_parser
|
|
3
|
+
from typing import Any, List
|
|
4
|
+
from argparse import ArgumentParser
|
|
5
|
+
from logging import basicConfig, getLogger
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from GooeyEx import Gooey, GooeyParser
|
|
10
|
+
except ImportError as e:
|
|
11
|
+
print(
|
|
12
|
+
"Please install sssekai[gui] through your Python package manager to use the GUI"
|
|
13
|
+
)
|
|
14
|
+
raise e
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from sssekai.unity import sssekai_set_unity_version
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@Gooey(
|
|
21
|
+
show_preview_warning=False,
|
|
22
|
+
program_name="sssekai",
|
|
23
|
+
tabbed_groups=True,
|
|
24
|
+
advanced=True,
|
|
25
|
+
monospace_display=True,
|
|
26
|
+
default_size=(800, 600),
|
|
27
|
+
menu=[
|
|
28
|
+
{
|
|
29
|
+
"name": "Help",
|
|
30
|
+
"items": [
|
|
31
|
+
{
|
|
32
|
+
"type": "Link",
|
|
33
|
+
"menuTitle": "Report an Issue",
|
|
34
|
+
"url": "https://github.com/MetaSekaiLab/sssekai/issues",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"type": "Link",
|
|
38
|
+
"menuTitle": "GitHub",
|
|
39
|
+
"url": "https://github.com/MetaSekaiLab/sssekai",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
def __main__():
|
|
46
|
+
from tqdm.std import tqdm as tqdm_c
|
|
47
|
+
|
|
48
|
+
parser = create_parser(GooeyParser)
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
class TqdmMutexStream:
|
|
52
|
+
@staticmethod
|
|
53
|
+
def write(__s):
|
|
54
|
+
# Gooey[Ex] only reads output from stdout so we'd do that here.
|
|
55
|
+
with tqdm_c.external_write_mode(file=sys.stdout, nolock=False):
|
|
56
|
+
return sys.stdout.write(__s)
|
|
57
|
+
|
|
58
|
+
basicConfig(
|
|
59
|
+
level="DEBUG",
|
|
60
|
+
format="%(asctime)s | %(levelname).1s | %(name)s %(message)s",
|
|
61
|
+
datefmt="%H:%M:%S",
|
|
62
|
+
stream=TqdmMutexStream,
|
|
63
|
+
)
|
|
64
|
+
# override unity version
|
|
65
|
+
sssekai_set_unity_version(args.unity_version)
|
|
66
|
+
if "func" in args:
|
|
67
|
+
args.func(args)
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ in {"__main__", "__gui__"}:
|
|
72
|
+
__main__()
|
sssekai/__init__.py
ADDED
sssekai/__main__.py
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import argparse, sys, logging, traceback
|
|
2
|
+
|
|
3
|
+
# region Deferred imports
|
|
4
|
+
# This can be done a lot cleaner with importlib. But tools like PyInstaller really
|
|
5
|
+
# won't like that.
|
|
6
|
+
|
|
7
|
+
def main_apidecrypt(*args, **kwargs):
|
|
8
|
+
from sssekai.entrypoint.apidecrypt import main_apidecrypt
|
|
9
|
+
return main_apidecrypt(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
def main_abdecrypt(*args, **kwargs):
|
|
12
|
+
from sssekai.entrypoint.abdecrypt import main_abdecrypt
|
|
13
|
+
return main_abdecrypt(*args, **kwargs)
|
|
14
|
+
|
|
15
|
+
def main_rla2json(*args, **kwargs):
|
|
16
|
+
from sssekai.entrypoint.rla2json import main_rla2json
|
|
17
|
+
return main_rla2json(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
def main_usmdemux(*args, **kwargs):
|
|
20
|
+
from sssekai.entrypoint.usmdemux import main_usmdemux
|
|
21
|
+
return main_usmdemux(*args, **kwargs)
|
|
22
|
+
|
|
23
|
+
def main_abcache(*args, **kwargs):
|
|
24
|
+
from sssekai.entrypoint.abcache import main_abcache
|
|
25
|
+
return main_abcache(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
def main_abserve(*args, **kwargs):
|
|
28
|
+
from sssekai.entrypoint.abserve import main_abserve
|
|
29
|
+
return main_abserve(*args, **kwargs)
|
|
30
|
+
|
|
31
|
+
def main_live2dextract(*args, **kwargs):
|
|
32
|
+
from sssekai.entrypoint.live2dextract import main_live2dextract
|
|
33
|
+
return main_live2dextract(*args, **kwargs)
|
|
34
|
+
|
|
35
|
+
def main_spineextract(*args, **kwargs):
|
|
36
|
+
from sssekai.entrypoint.spineextract import main_spineextract
|
|
37
|
+
return main_spineextract(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
def main_apphash(*args, **kwargs):
|
|
40
|
+
from sssekai.entrypoint.apphash import main_apphash
|
|
41
|
+
return main_apphash(*args, **kwargs)
|
|
42
|
+
|
|
43
|
+
def main_il2cpp(*args, **kwargs):
|
|
44
|
+
from sssekai.entrypoint.il2cpp import main_il2cpp
|
|
45
|
+
return main_il2cpp(*args, **kwargs)
|
|
46
|
+
|
|
47
|
+
def main_il2cppextract(*args, **kwargs):
|
|
48
|
+
from sssekai.entrypoint.il2cppextract import main_il2cppextract
|
|
49
|
+
return main_il2cppextract(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
def main_mvdata(*args, **kwargs):
|
|
52
|
+
from sssekai.entrypoint.mvdata import main_mvdata
|
|
53
|
+
return main_mvdata(*args, **kwargs)
|
|
54
|
+
|
|
55
|
+
def main_moc3paths(*args, **kwargs):
|
|
56
|
+
from sssekai.entrypoint.moc3paths import main_moc3paths
|
|
57
|
+
return main_moc3paths(*args, **kwargs)
|
|
58
|
+
|
|
59
|
+
# endregion
|
|
60
|
+
|
|
61
|
+
from sssekai.unity import sssekai_get_unity_version, sssekai_set_unity_version
|
|
62
|
+
from sssekai.fmt.rla import RLA_VERSIONS
|
|
63
|
+
|
|
64
|
+
logger = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_parser(clazz=argparse.ArgumentParser):
|
|
68
|
+
"""
|
|
69
|
+
Create the command line parser for the application.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
clazz (type): The parser class to use. Defaults to argparse.ArgumentParser.
|
|
73
|
+
In GUI mode this should be set to GooeyParser otherwise.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def gooey_only(**kwargs):
|
|
77
|
+
# Silent non-argparse kwargs
|
|
78
|
+
if clazz == argparse.ArgumentParser:
|
|
79
|
+
return {}
|
|
80
|
+
return kwargs
|
|
81
|
+
|
|
82
|
+
parser = clazz(
|
|
83
|
+
description="""Project SEKAI Asset Utility / PJSK 资源工具""",
|
|
84
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--log-level",
|
|
88
|
+
type=str,
|
|
89
|
+
help="logging level (default: %(default)s)",
|
|
90
|
+
default="INFO",
|
|
91
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
92
|
+
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"--unity-version",
|
|
95
|
+
type=str,
|
|
96
|
+
help="""Unity version to use (default: %(default)s)
|
|
97
|
+
Prior to game version 3.6.0 (JP), this has always been 2020.3.21f1
|
|
98
|
+
This has been changed to 2022.3.21f1 since, which would apply to all the assets from 3.6.0 onwards.
|
|
99
|
+
If you encounter any issues, try switching to the old version, or vice versa.""",
|
|
100
|
+
default=sssekai_get_unity_version(),
|
|
101
|
+
)
|
|
102
|
+
subparsers = parser.add_subparsers(
|
|
103
|
+
title="subcommands", description="valid subcommands", help="additional help"
|
|
104
|
+
)
|
|
105
|
+
# apidecrypt
|
|
106
|
+
apidecrypt_parser = subparsers.add_parser(
|
|
107
|
+
"apidecrypt",
|
|
108
|
+
usage="""API crypto dumper
|
|
109
|
+
This crypto applies to:
|
|
110
|
+
- API request/response body dumped by packet sniffer (mitmproxy, wireshark, etc.)
|
|
111
|
+
- AssetBundleInfo (see sssekai.abcache)""",
|
|
112
|
+
)
|
|
113
|
+
apidecrypt_parser.add_argument(
|
|
114
|
+
"infile", type=str, help="input dump file", **gooey_only(widget="FileChooser")
|
|
115
|
+
)
|
|
116
|
+
apidecrypt_parser.add_argument(
|
|
117
|
+
"outfile", type=str, help="output json file", **gooey_only(widget="FileSaver")
|
|
118
|
+
)
|
|
119
|
+
apidecrypt_parser.add_argument(
|
|
120
|
+
"--region",
|
|
121
|
+
type=str,
|
|
122
|
+
help="app region",
|
|
123
|
+
default="jp",
|
|
124
|
+
choices=["jp", "tw", "en", "kr", "cn"],
|
|
125
|
+
)
|
|
126
|
+
apidecrypt_parser.set_defaults(func=main_apidecrypt)
|
|
127
|
+
# abdecrypt
|
|
128
|
+
abdecrypt_parser = subparsers.add_parser(
|
|
129
|
+
"abdecrypt", usage="""Decrypt Sekai AssetBundle"""
|
|
130
|
+
)
|
|
131
|
+
abdecrypt_parser.add_argument(
|
|
132
|
+
"indir", type=str, help="input directory", **gooey_only(widget="DirChooser")
|
|
133
|
+
)
|
|
134
|
+
abdecrypt_parser.add_argument(
|
|
135
|
+
"outdir", type=str, help="output directory", **gooey_only(widget="DirChooser")
|
|
136
|
+
)
|
|
137
|
+
abdecrypt_parser.set_defaults(func=main_abdecrypt)
|
|
138
|
+
# usmdemux
|
|
139
|
+
usmdemux_parser = subparsers.add_parser(
|
|
140
|
+
"usmdemux", usage="""Demux Sekai USM Video in a AssetBundle"""
|
|
141
|
+
)
|
|
142
|
+
usmdemux_parser.add_argument(
|
|
143
|
+
"infile", type=str, help="input file", **gooey_only(widget="FileChooser")
|
|
144
|
+
)
|
|
145
|
+
usmdemux_parser.add_argument(
|
|
146
|
+
"outfile", type=str, help="output file where the .USM file will be saved", **gooey_only(widget="FileSaver")
|
|
147
|
+
)
|
|
148
|
+
usmdemux_parser.set_defaults(func=main_usmdemux)
|
|
149
|
+
# abcache
|
|
150
|
+
abcache_parser = subparsers.add_parser(
|
|
151
|
+
"abcache", usage="""Sekai AssetBundle Metadata Cache / Game API Helper"""
|
|
152
|
+
)
|
|
153
|
+
abcache_parser.add_argument(
|
|
154
|
+
"--proxy",
|
|
155
|
+
type=str,
|
|
156
|
+
help="HTTP Proxy to use. This overrides the system proxy (environ HTTP_PROXY, HTTPS_PROXY) settings.",
|
|
157
|
+
default=None,
|
|
158
|
+
)
|
|
159
|
+
group = abcache_parser.add_argument_group("save/load options")
|
|
160
|
+
group.add_argument(
|
|
161
|
+
"--db",
|
|
162
|
+
type=str,
|
|
163
|
+
help="""cache database file path (default: %(default)s)""",
|
|
164
|
+
default="~/.sssekai/abcache.db",
|
|
165
|
+
**gooey_only(widget="FileChooser"),
|
|
166
|
+
)
|
|
167
|
+
group.add_argument(
|
|
168
|
+
"--no-update",
|
|
169
|
+
action="store_true",
|
|
170
|
+
help="skip all metadata updates and use cached ones as is. this supercedes 'game version options' and 'authentication options'",
|
|
171
|
+
)
|
|
172
|
+
group.add_argument(
|
|
173
|
+
"--dump",
|
|
174
|
+
type=str,
|
|
175
|
+
help="dump the AssetBundle index to the specified JSON file and exit",
|
|
176
|
+
default=None,
|
|
177
|
+
**gooey_only(widget="FileSaver"),
|
|
178
|
+
)
|
|
179
|
+
group = abcache_parser.add_argument_group(
|
|
180
|
+
"game version options",
|
|
181
|
+
"NOTE: these options are used to *update* the cache database. Use --no-update to skip updating. Also, you may want to read the Wiki to know how to get these values.",
|
|
182
|
+
)
|
|
183
|
+
group.add_argument(
|
|
184
|
+
"--app-region",
|
|
185
|
+
type=str,
|
|
186
|
+
help="PJSK app region (default: %(default)s)",
|
|
187
|
+
choices=["jp", "tw", "en", "kr", "cn"],
|
|
188
|
+
default="jp",
|
|
189
|
+
)
|
|
190
|
+
group.add_argument(
|
|
191
|
+
"--app-platform",
|
|
192
|
+
type=str,
|
|
193
|
+
help="PJSK app platform (default: %(default)s",
|
|
194
|
+
choices=["ios", "android"],
|
|
195
|
+
default="android",
|
|
196
|
+
) # Let's hope this doesn't age well...
|
|
197
|
+
group.add_argument(
|
|
198
|
+
"--app-version",
|
|
199
|
+
type=str,
|
|
200
|
+
help="PJSK app version. For assets this is required unless --no-update is specified, or for EN/JP requisite --app-asset..fields are filled. For other functions this is required unless --no-update is specified",
|
|
201
|
+
default="",
|
|
202
|
+
)
|
|
203
|
+
group.add_argument(
|
|
204
|
+
"--app-appHash",
|
|
205
|
+
type=str,
|
|
206
|
+
help="PJSK app hash. For assets this is required unless --no-update is specified, or for EN/JP requisite --app-asset..fields are filled. For other functions this is required unless --no-update is specified",
|
|
207
|
+
default="",
|
|
208
|
+
)
|
|
209
|
+
group = abcache_parser.add_argument_group("ROW specific options")
|
|
210
|
+
group.add_argument(
|
|
211
|
+
"--app-abVersion",
|
|
212
|
+
type=str,
|
|
213
|
+
help="PJSK AssetBundle URL version. ROW servers only - This is required unless --no-update is specified",
|
|
214
|
+
default=None,
|
|
215
|
+
)
|
|
216
|
+
group = abcache_parser.add_argument_group("EN/JP specific options")
|
|
217
|
+
group.add_argument(
|
|
218
|
+
"--app-asset-host",
|
|
219
|
+
type=str,
|
|
220
|
+
help="Override AssetBundle host URL. EN/JP servers only.",
|
|
221
|
+
default=None,
|
|
222
|
+
)
|
|
223
|
+
group.add_argument(
|
|
224
|
+
"--app-asset-version",
|
|
225
|
+
type=str,
|
|
226
|
+
help="Override AssetBundle source version. EN/JP servers only.",
|
|
227
|
+
default=None,
|
|
228
|
+
)
|
|
229
|
+
group.add_argument(
|
|
230
|
+
"--app-asset-hash",
|
|
231
|
+
type=str,
|
|
232
|
+
help="Override AssetBundle hash. EN/JP servers only.",
|
|
233
|
+
default=None,
|
|
234
|
+
)
|
|
235
|
+
group = abcache_parser.add_argument_group("download options")
|
|
236
|
+
group.add_argument(
|
|
237
|
+
"--download-no-overwrite",
|
|
238
|
+
action="store_true",
|
|
239
|
+
help="skip downloading AssetBundles that are already present in the download directory",
|
|
240
|
+
)
|
|
241
|
+
group.add_argument(
|
|
242
|
+
"--download-filter",
|
|
243
|
+
type=str,
|
|
244
|
+
help="filter AssetBundles (by bundle names) with this regex pattern",
|
|
245
|
+
default=None,
|
|
246
|
+
)
|
|
247
|
+
group.add_argument(
|
|
248
|
+
"--download-filter-cache-diff",
|
|
249
|
+
type=str,
|
|
250
|
+
help="filter AssetBundles (by hashes) by *ONLY* downloading ones with a *different* hash from the current hash with this one.",
|
|
251
|
+
default=None,
|
|
252
|
+
**gooey_only(widget="FileChooser"),
|
|
253
|
+
)
|
|
254
|
+
group.add_argument(
|
|
255
|
+
"--download-dir",
|
|
256
|
+
type=str,
|
|
257
|
+
help="asset bundle download directory. leave empty if you don't want to download anything",
|
|
258
|
+
default="",
|
|
259
|
+
**gooey_only(widget="DirChooser"),
|
|
260
|
+
)
|
|
261
|
+
group.add_argument(
|
|
262
|
+
"--download-ensure-deps",
|
|
263
|
+
action="store_true",
|
|
264
|
+
help="ensure dependencies (of the downloaded ones) are downloaded as well",
|
|
265
|
+
)
|
|
266
|
+
group.add_argument(
|
|
267
|
+
"--download-workers",
|
|
268
|
+
type=int,
|
|
269
|
+
help="number of download workers (default: %(default)s)",
|
|
270
|
+
default=4,
|
|
271
|
+
)
|
|
272
|
+
group = abcache_parser.add_argument_group(
|
|
273
|
+
"extra options",
|
|
274
|
+
"NOTE: when *any* of these options are specified, the cache database *won't* be updated, and no download will be performed either.",
|
|
275
|
+
)
|
|
276
|
+
group.add_argument(
|
|
277
|
+
"--dump-master-data",
|
|
278
|
+
type=str,
|
|
279
|
+
help="directory to store the dumped master data in JSON.",
|
|
280
|
+
default=None,
|
|
281
|
+
**gooey_only(widget="DirChooser"),
|
|
282
|
+
)
|
|
283
|
+
group.add_argument(
|
|
284
|
+
"--dump-user-data",
|
|
285
|
+
type=str,
|
|
286
|
+
help="directory to store the dumped master data of the authencicated user in JSON. NOTE: --auth-credential required",
|
|
287
|
+
default=None,
|
|
288
|
+
**gooey_only(widget="DirChooser"),
|
|
289
|
+
)
|
|
290
|
+
group.add_argument(
|
|
291
|
+
"--keep-compact",
|
|
292
|
+
help="keep compacted datasets as is without expanding them to key-value pairs",
|
|
293
|
+
action="store_true",
|
|
294
|
+
)
|
|
295
|
+
group = abcache_parser.add_argument_group(
|
|
296
|
+
"authentication arguments",
|
|
297
|
+
"Only needed for some functionailties (i.e. --dump-user-data)",
|
|
298
|
+
)
|
|
299
|
+
group.add_argument(
|
|
300
|
+
"--auth-credential",
|
|
301
|
+
type=str,
|
|
302
|
+
help="Credential for API authentication. Can be omitted on JP/EN servers, in which case a guest account would be automatically registered and used.",
|
|
303
|
+
default=None,
|
|
304
|
+
)
|
|
305
|
+
abcache_parser.set_defaults(func=main_abcache)
|
|
306
|
+
abserve_parser = subparsers.add_parser(
|
|
307
|
+
"abserve", usage="""AbCache Filesystem Server / Backend"""
|
|
308
|
+
)
|
|
309
|
+
abserve_parser.add_argument(
|
|
310
|
+
"--proxy",
|
|
311
|
+
type=str,
|
|
312
|
+
help="HTTP Proxy to use. This overrides the system proxy (environ HTTP_PROXY, HTTPS_PROXY) settings.",
|
|
313
|
+
default=None,
|
|
314
|
+
)
|
|
315
|
+
abserve_parser.add_argument(
|
|
316
|
+
"--db",
|
|
317
|
+
type=str,
|
|
318
|
+
help="""cache database file path (default: %(default)s)""",
|
|
319
|
+
default="~/.sssekai/abcache.db",
|
|
320
|
+
**gooey_only(widget="FileChooser"),
|
|
321
|
+
)
|
|
322
|
+
abserve_parser.add_argument(
|
|
323
|
+
"--fuse",
|
|
324
|
+
type=str,
|
|
325
|
+
help="""local mount point for AbCache. Required FUSE on host OS and fusepy (default: %(default)s)""",
|
|
326
|
+
default="",
|
|
327
|
+
**gooey_only(widget="DirChooser"),
|
|
328
|
+
)
|
|
329
|
+
abserve_parser.add_argument(
|
|
330
|
+
"--host",
|
|
331
|
+
type=str,
|
|
332
|
+
help="""address of the interface to listen on (default: %(default)s)""",
|
|
333
|
+
default="0.0.0.0",
|
|
334
|
+
)
|
|
335
|
+
abserve_parser.add_argument(
|
|
336
|
+
"--port",
|
|
337
|
+
type=int,
|
|
338
|
+
help="""port to listen on (default: %(default)s)""",
|
|
339
|
+
default="3939",
|
|
340
|
+
)
|
|
341
|
+
abserve_parser.set_defaults(func=main_abserve)
|
|
342
|
+
# live2dextract
|
|
343
|
+
live2dextract_parser = subparsers.add_parser(
|
|
344
|
+
"live2dextract", usage="""Extract Sekai Live2D Models in a AssetBundle"""
|
|
345
|
+
)
|
|
346
|
+
live2dextract_parser.add_argument(
|
|
347
|
+
"infile", type=str, help="input file", **gooey_only(widget="FileChooser")
|
|
348
|
+
)
|
|
349
|
+
live2dextract_parser.add_argument(
|
|
350
|
+
"outdir", type=str, help="output directory", **gooey_only(widget="DirChooser")
|
|
351
|
+
)
|
|
352
|
+
live2dextract_parser.add_argument(
|
|
353
|
+
"--no-anim", action="store_true", help="don't extract animation clips"
|
|
354
|
+
)
|
|
355
|
+
live2dextract_parser.set_defaults(func=main_live2dextract)
|
|
356
|
+
# spineextract
|
|
357
|
+
spineextract_parser = subparsers.add_parser(
|
|
358
|
+
"spineextract",
|
|
359
|
+
usage="""Extract Sekai Spine (Esoteric Spine2D) Models in a AssetBundle""",
|
|
360
|
+
)
|
|
361
|
+
spineextract_parser.add_argument(
|
|
362
|
+
"infile", type=str, help="input file", **gooey_only(widget="FileChooser")
|
|
363
|
+
)
|
|
364
|
+
spineextract_parser.add_argument(
|
|
365
|
+
"outdir", type=str, help="output directory", **gooey_only(widget="DirChooser")
|
|
366
|
+
)
|
|
367
|
+
spineextract_parser.set_defaults(func=main_spineextract)
|
|
368
|
+
# rla2json
|
|
369
|
+
rla2json_parser = subparsers.add_parser(
|
|
370
|
+
"rla2json",
|
|
371
|
+
usage="""Read streaming_live/archive files and dump their information to JSON or HCA packets""",
|
|
372
|
+
)
|
|
373
|
+
rla2json_parser.add_argument(
|
|
374
|
+
"input",
|
|
375
|
+
type=str,
|
|
376
|
+
help="input archive file or directory containing loose packets",
|
|
377
|
+
**gooey_only(widget="FileChooser"),
|
|
378
|
+
)
|
|
379
|
+
rla2json_parser.add_argument(
|
|
380
|
+
"outdir", type=str, help="output directory", **gooey_only(widget="DirChooser")
|
|
381
|
+
)
|
|
382
|
+
rla2json_parser.add_argument(
|
|
383
|
+
"--version",
|
|
384
|
+
type=str,
|
|
385
|
+
help="RLA version. Only used for loose packets",
|
|
386
|
+
default="1.6",
|
|
387
|
+
choices=["%1s.%1s" % x for x in RLA_VERSIONS],
|
|
388
|
+
)
|
|
389
|
+
rla2json_parser.add_argument(
|
|
390
|
+
"--strict",
|
|
391
|
+
action="store_true",
|
|
392
|
+
help="strict mode. raise exception on unknown frame type or corrupt frames",
|
|
393
|
+
)
|
|
394
|
+
rla2json_parser.set_defaults(func=main_rla2json)
|
|
395
|
+
# apphash
|
|
396
|
+
apphash_parser = subparsers.add_parser(
|
|
397
|
+
"apphash",
|
|
398
|
+
usage="""Download/extract game AppHash values
|
|
399
|
+
*NOTE*: Only [app version] [app region] [app hash] are written to stdout if successful. Fields are separated by space. Unavailable fields are left as 'unknown'.""",
|
|
400
|
+
)
|
|
401
|
+
apphash_parser.add_argument(
|
|
402
|
+
"--apk-src",
|
|
403
|
+
type=str,
|
|
404
|
+
help="APK source file (default: fetch from APKPure)",
|
|
405
|
+
default=None,
|
|
406
|
+
**gooey_only(widget="FileChooser"),
|
|
407
|
+
)
|
|
408
|
+
apphash_parser.add_argument(
|
|
409
|
+
"--ab-src",
|
|
410
|
+
type=str,
|
|
411
|
+
help="AssetBundle (namely, 6350e2ec327334c8a9b7f494f344a761 or c726e51b6fe37463685916a1687158dd) source file (default: fetch from APK)",
|
|
412
|
+
default=None,
|
|
413
|
+
**gooey_only(widget="FileChooser"),
|
|
414
|
+
)
|
|
415
|
+
apphash_parser.add_argument(
|
|
416
|
+
"--fetch", action="store_true", help="force fetching the latest APK"
|
|
417
|
+
)
|
|
418
|
+
apphash_parser.add_argument(
|
|
419
|
+
"--format",
|
|
420
|
+
choices=["markdown", "json"],
|
|
421
|
+
help="output format",
|
|
422
|
+
default="markdown",
|
|
423
|
+
)
|
|
424
|
+
apphash_parser.add_argument(
|
|
425
|
+
"--proxy",
|
|
426
|
+
type=str,
|
|
427
|
+
help="HTTP Proxy to use. This overrides the system proxy (environ HTTP_PROXY, HTTPS_PROXY) settings.",
|
|
428
|
+
default=None,
|
|
429
|
+
)
|
|
430
|
+
apphash_parser.add_argument(
|
|
431
|
+
"--deep",
|
|
432
|
+
action="store_true",
|
|
433
|
+
help="Load all AssetBundles and retrieve all app hashes available.",
|
|
434
|
+
)
|
|
435
|
+
apphash_parser.set_defaults(func=main_apphash)
|
|
436
|
+
# il2cpp
|
|
437
|
+
il2cpp_parser = subparsers.add_parser(
|
|
438
|
+
"il2cpp",
|
|
439
|
+
usage="""extract il2cpp binary with deobfuscated metadata from **JP Android** releases""",
|
|
440
|
+
)
|
|
441
|
+
il2cpp_parser.add_argument(
|
|
442
|
+
"metadata",
|
|
443
|
+
type=str,
|
|
444
|
+
help="the global-metadata.dat file at com.sega.pjsekai/assets/bin/Data/Managed/Metadata",
|
|
445
|
+
**gooey_only(widget="FileChooser"),
|
|
446
|
+
)
|
|
447
|
+
il2cpp_parser.add_argument(
|
|
448
|
+
"binary",
|
|
449
|
+
type=str,
|
|
450
|
+
help="libil2cpp.so at config.arm64_v8a/lib/arm64-v8a. currently this would be copied as is",
|
|
451
|
+
**gooey_only(widget="FileChooser"),
|
|
452
|
+
)
|
|
453
|
+
il2cpp_parser.add_argument(
|
|
454
|
+
"outdir",
|
|
455
|
+
type=str,
|
|
456
|
+
help="output directory to dump the extracted metadata (as global-metadata.dat) and binary (as il2cpp.so)",
|
|
457
|
+
**gooey_only(widget="DirChooser"),
|
|
458
|
+
)
|
|
459
|
+
il2cpp_parser.set_defaults(func=main_il2cpp)
|
|
460
|
+
il2cppextract_parser = subparsers.add_parser(
|
|
461
|
+
"il2cpp-extract",
|
|
462
|
+
help="Prepare IL2CPP analysis inputs from a JP Android ARM64 APK/XAPK",
|
|
463
|
+
description="Extract APK/XAPK inputs and run the existing il2cpp command.",
|
|
464
|
+
)
|
|
465
|
+
il2cppextract_parser.add_argument(
|
|
466
|
+
"infile", help="input APK or XAPK (including ARM64 splits)",
|
|
467
|
+
**gooey_only(widget="FileChooser"),
|
|
468
|
+
)
|
|
469
|
+
il2cppextract_parser.add_argument(
|
|
470
|
+
"outdir", help="output analysis directory",
|
|
471
|
+
**gooey_only(widget="DirChooser"),
|
|
472
|
+
)
|
|
473
|
+
il2cppextract_parser.set_defaults(func=main_il2cppextract)
|
|
474
|
+
# mvdata
|
|
475
|
+
mvdata_parser = subparsers.add_parser(
|
|
476
|
+
"mvdata", usage="""Extract MV Data from AssetBundle"""
|
|
477
|
+
)
|
|
478
|
+
mvdata_parser.add_argument(
|
|
479
|
+
"indir",
|
|
480
|
+
type=str,
|
|
481
|
+
help="cache directory (live_pv/mv_data)",
|
|
482
|
+
**gooey_only(widget="DirChooser"),
|
|
483
|
+
)
|
|
484
|
+
mvdata_parser.add_argument(
|
|
485
|
+
"outfile",
|
|
486
|
+
type=str,
|
|
487
|
+
help="output JSON file to dump into",
|
|
488
|
+
**gooey_only(widget="FileSaver"),
|
|
489
|
+
)
|
|
490
|
+
mvdata_parser.set_defaults(func=main_mvdata)
|
|
491
|
+
# moc3paths
|
|
492
|
+
moc3paths_parser = subparsers.add_parser(
|
|
493
|
+
"moc3paths", usage="""Extract animation path CRCs from raw .moc3 binaries"""
|
|
494
|
+
)
|
|
495
|
+
moc3paths_parser.add_argument(
|
|
496
|
+
"indir", type=str, help="input directory", **gooey_only(widget="DirChooser")
|
|
497
|
+
)
|
|
498
|
+
moc3paths_parser.set_defaults(func=main_moc3paths)
|
|
499
|
+
return parser
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def __main__():
|
|
503
|
+
from tqdm.std import tqdm as tqdm_c
|
|
504
|
+
|
|
505
|
+
class TqdmMutexStream:
|
|
506
|
+
@staticmethod
|
|
507
|
+
def write(__s):
|
|
508
|
+
with tqdm_c.external_write_mode(file=sys.stderr, nolock=False):
|
|
509
|
+
return sys.stderr.write(__s)
|
|
510
|
+
|
|
511
|
+
# parse args
|
|
512
|
+
parser = create_parser(argparse.ArgumentParser)
|
|
513
|
+
args = parser.parse_args()
|
|
514
|
+
# set logging level
|
|
515
|
+
from logging import basicConfig
|
|
516
|
+
|
|
517
|
+
basicConfig(
|
|
518
|
+
level=args.log_level,
|
|
519
|
+
format="%(asctime)s | %(levelname).1s | %(name)s %(message)s",
|
|
520
|
+
datefmt="%H:%M:%S",
|
|
521
|
+
stream=TqdmMutexStream,
|
|
522
|
+
)
|
|
523
|
+
# override unity version
|
|
524
|
+
sssekai_set_unity_version(args.unity_version)
|
|
525
|
+
if "func" in args:
|
|
526
|
+
try:
|
|
527
|
+
return args.func(args)
|
|
528
|
+
except Exception as e:
|
|
529
|
+
logger.exception("Error while running command: %s", e)
|
|
530
|
+
traceback.print_exc()
|
|
531
|
+
return 1
|
|
532
|
+
else:
|
|
533
|
+
parser.print_help()
|
|
534
|
+
return 1
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
if __name__ == "__main__":
|
|
538
|
+
import coloredlogs
|
|
539
|
+
|
|
540
|
+
coloredlogs.install(
|
|
541
|
+
level=args.log_level,
|
|
542
|
+
format="%(asctime)s | %(levelname).1s | %(name)s %(message)s",
|
|
543
|
+
datefmt="%H:%M:%S",
|
|
544
|
+
isatty=True,
|
|
545
|
+
stream=TqdmMutexStream,
|
|
546
|
+
)
|
|
547
|
+
sys.exit(__main__() or 0)
|