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.
Files changed (40) hide show
  1. sssekai/__gui__.py +72 -0
  2. sssekai/__init__.py +6 -0
  3. sssekai/__main__.py +547 -0
  4. sssekai/abcache/__init__.py +771 -0
  5. sssekai/abcache/auth.py +30 -0
  6. sssekai/abcache/fs.py +275 -0
  7. sssekai/crypto/APIManager.py +42 -0
  8. sssekai/crypto/AssetBundle.py +26 -0
  9. sssekai/crypto/__init__.py +0 -0
  10. sssekai/entrypoint/__init__.py +0 -0
  11. sssekai/entrypoint/abcache.py +299 -0
  12. sssekai/entrypoint/abdecrypt.py +26 -0
  13. sssekai/entrypoint/abserve.py +123 -0
  14. sssekai/entrypoint/apidecrypt.py +20 -0
  15. sssekai/entrypoint/apphash.py +276 -0
  16. sssekai/entrypoint/il2cpp.py +45 -0
  17. sssekai/entrypoint/il2cppextract.py +52 -0
  18. sssekai/entrypoint/live2dextract.py +74 -0
  19. sssekai/entrypoint/moc3paths.py +38 -0
  20. sssekai/entrypoint/mvdata.py +34 -0
  21. sssekai/entrypoint/rla2json.py +79 -0
  22. sssekai/entrypoint/spineextract.py +64 -0
  23. sssekai/entrypoint/usmdemux.py +29 -0
  24. sssekai/fmt/__init__.py +0 -0
  25. sssekai/fmt/moc3.py +47 -0
  26. sssekai/fmt/motion3.py +92 -0
  27. sssekai/fmt/rla.py +516 -0
  28. sssekai/generated/Sekai/__init__.py +66 -0
  29. sssekai/generated/__init__.py +96 -0
  30. sssekai/unity/AnimationClip.py +668 -0
  31. sssekai/unity/AssetBundle.py +13 -0
  32. sssekai/unity/__init__.py +19 -0
  33. sssekai/unity/constant/SekaiLive2DPathNames.py +1397 -0
  34. sssekai/unity/constant/__init__.py +0 -0
  35. sssekai-0.8.1.dist-info/METADATA +86 -0
  36. sssekai-0.8.1.dist-info/RECORD +40 -0
  37. sssekai-0.8.1.dist-info/WHEEL +5 -0
  38. sssekai-0.8.1.dist-info/entry_points.txt +6 -0
  39. sssekai-0.8.1.dist-info/licenses/LICENSE +21 -0
  40. 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
@@ -0,0 +1,6 @@
1
+ __VERSION_MAJOR__ = 0
2
+ __VERSION_MINOR__ = 8
3
+ __VERSION_PATCH__ = 1
4
+
5
+ __version_tuple__ = (__VERSION_MAJOR__, __VERSION_MINOR__, __VERSION_PATCH__)
6
+ __version__ = "%s.%s.%s" % __version_tuple__
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)