sssekai 0.0.1__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.

Potentially problematic release.


This version of sssekai might be problematic. Click here for more details.

Files changed (36) hide show
  1. sssekai-0.0.1/PKG-INFO +64 -0
  2. sssekai-0.0.1/README.md +48 -0
  3. sssekai-0.0.1/setup.cfg +4 -0
  4. sssekai-0.0.1/setup.py +27 -0
  5. sssekai-0.0.1/sssekai/__init__.py +6 -0
  6. sssekai-0.0.1/sssekai/__main__.py +77 -0
  7. sssekai-0.0.1/sssekai/abcache/__init__.py +188 -0
  8. sssekai-0.0.1/sssekai/crypto/APIManager.py +24 -0
  9. sssekai-0.0.1/sssekai/crypto/AssetBundle.py +32 -0
  10. sssekai-0.0.1/sssekai/crypto/__init__.py +0 -0
  11. sssekai-0.0.1/sssekai/entrypoint/__init__.py +0 -0
  12. sssekai-0.0.1/sssekai/entrypoint/abcache.py +15 -0
  13. sssekai-0.0.1/sssekai/entrypoint/abdecrypt.py +9 -0
  14. sssekai-0.0.1/sssekai/entrypoint/apidecrypt.py +9 -0
  15. sssekai-0.0.1/sssekai/entrypoint/live2dextract.py +165 -0
  16. sssekai-0.0.1/sssekai/entrypoint/mitm.py +7 -0
  17. sssekai-0.0.1/sssekai/entrypoint/usmdemux.py +32 -0
  18. sssekai-0.0.1/sssekai/fmt/__init__.py +0 -0
  19. sssekai-0.0.1/sssekai/fmt/moc3.py +40 -0
  20. sssekai-0.0.1/sssekai/unity/AnimationClip.py +170 -0
  21. sssekai-0.0.1/sssekai/unity/AssetBundle.py +13 -0
  22. sssekai-0.0.1/sssekai/unity/__init__.py +2 -0
  23. sssekai-0.0.1/sssekai/unity/constant/CommonPathNames.py +1 -0
  24. sssekai-0.0.1/sssekai/unity/constant/SekaiLive2DPathNames.py +1076 -0
  25. sssekai-0.0.1/sssekai/unity/constant/__init__.py +0 -0
  26. sssekai-0.0.1/sssekai.egg-info/PKG-INFO +64 -0
  27. sssekai-0.0.1/sssekai.egg-info/SOURCES.txt +34 -0
  28. sssekai-0.0.1/sssekai.egg-info/dependency_links.txt +1 -0
  29. sssekai-0.0.1/sssekai.egg-info/entry_points.txt +2 -0
  30. sssekai-0.0.1/sssekai.egg-info/requires.txt +4 -0
  31. sssekai-0.0.1/sssekai.egg-info/top_level.txt +2 -0
  32. sssekai-0.0.1/sssekai_blender_io/__init__.py +29 -0
  33. sssekai-0.0.1/sssekai_blender_io/blender/__init__.py +109 -0
  34. sssekai-0.0.1/sssekai_blender_io/blender/addon.py +211 -0
  35. sssekai-0.0.1/sssekai_blender_io/blender/animation.py +123 -0
  36. sssekai-0.0.1/sssekai_blender_io/blender/asset.py +306 -0
sssekai-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.1
2
+ Name: sssekai
3
+ Version: 0.0.1
4
+ Home-page: https://github.com/mos9527/sssekai
5
+ Author: greats3an
6
+ Author-email: greats3an@gmail.com
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: msgpack
13
+ Requires-Dist: pycryptodome
14
+ Requires-Dist: unitypy
15
+ Requires-Dist: wannacri
16
+
17
+ # sssekai
18
+ Command-line tool for downloading/deobfuscating the game's assets, along with some other tools.
19
+
20
+ usage: sssekai [-h]
21
+ {apidecrypt,abdecrypt,usmdemux,abcache,live2dextract,mitm} ...
22
+
23
+ SSSekai Proejct SEKAI feat. Hatsune Miku (Android) Modding Tools
24
+ Installation:
25
+ pip install git+https://github.com/mos9527/sssekai
26
+
27
+ options:
28
+ -h, --help show this help message and exit
29
+
30
+ subcommands:
31
+ valid subcommands
32
+
33
+ {apidecrypt,abdecrypt,usmdemux,abcache,live2dextract,mitm}
34
+ additional help
35
+ apidecrypt API crypto dumper
36
+ This crypto applies to:
37
+ - API request/response body dumped by packet sniffer (mitmproxy, wireshark, etc.)
38
+ - AssetBundleInfo (can be found at /sdcard/Android/data/com.hermes.mk.asia/files/data/AssetBundleInfo,or see sssekai.abcache)
39
+ abdecrypt Decrypt Sekai AssetBundle
40
+ These can be found at /sdcard/Android/data/com.hermes.mk.asia/files/data/
41
+ usmdemux Demux Sekai USM Video in a AssetBundle
42
+ abcache Sekai AssetBundle local cache
43
+ Downloads/Updates *ALL* PJSK TW assets to local devices.
44
+ NOTE: The assets can take quite a lot of space (est. 27GB) so be prepared
45
+ NOTE: The AssetBundles cached are NOT OBFUSCATED. They can be used as is by various Unity ripping tools (and sssekai by extension)
46
+ that supports stripped Unity version (should be 2020.3.21f1).
47
+ live2dextract Extract Sekai Live2D Models in a AssetBundle
48
+ mitm Run Sekai API MITM proxy (WIP)
49
+
50
+ # sssekai_blender_io
51
+ Blender I/O for the game's assets. (actually it's imports only)
52
+
53
+ Refer to https://github.com/mos9527/sssekai for downloads!
54
+
55
+ Currently supports:
56
+ * Textures
57
+ * Character NPR Material (via [SekaiShaderStandalone](https://github.com/mos9527/sssekai/blob/main/sssekai_blender_io/assets/SekaiShaderStandalone.blend))
58
+ * Static Mesh
59
+ * Skinned Mesh
60
+ * Armatures (built through GameObject hierarchy)
61
+ * Animations (Skeletal, BlendShape)
62
+
63
+ ## Installation:
64
+ *WIP*
@@ -0,0 +1,48 @@
1
+ # sssekai
2
+ Command-line tool for downloading/deobfuscating the game's assets, along with some other tools.
3
+
4
+ usage: sssekai [-h]
5
+ {apidecrypt,abdecrypt,usmdemux,abcache,live2dextract,mitm} ...
6
+
7
+ SSSekai Proejct SEKAI feat. Hatsune Miku (Android) Modding Tools
8
+ Installation:
9
+ pip install git+https://github.com/mos9527/sssekai
10
+
11
+ options:
12
+ -h, --help show this help message and exit
13
+
14
+ subcommands:
15
+ valid subcommands
16
+
17
+ {apidecrypt,abdecrypt,usmdemux,abcache,live2dextract,mitm}
18
+ additional help
19
+ apidecrypt API crypto dumper
20
+ This crypto applies to:
21
+ - API request/response body dumped by packet sniffer (mitmproxy, wireshark, etc.)
22
+ - AssetBundleInfo (can be found at /sdcard/Android/data/com.hermes.mk.asia/files/data/AssetBundleInfo,or see sssekai.abcache)
23
+ abdecrypt Decrypt Sekai AssetBundle
24
+ These can be found at /sdcard/Android/data/com.hermes.mk.asia/files/data/
25
+ usmdemux Demux Sekai USM Video in a AssetBundle
26
+ abcache Sekai AssetBundle local cache
27
+ Downloads/Updates *ALL* PJSK TW assets to local devices.
28
+ NOTE: The assets can take quite a lot of space (est. 27GB) so be prepared
29
+ NOTE: The AssetBundles cached are NOT OBFUSCATED. They can be used as is by various Unity ripping tools (and sssekai by extension)
30
+ that supports stripped Unity version (should be 2020.3.21f1).
31
+ live2dextract Extract Sekai Live2D Models in a AssetBundle
32
+ mitm Run Sekai API MITM proxy (WIP)
33
+
34
+ # sssekai_blender_io
35
+ Blender I/O for the game's assets. (actually it's imports only)
36
+
37
+ Refer to https://github.com/mos9527/sssekai for downloads!
38
+
39
+ Currently supports:
40
+ * Textures
41
+ * Character NPR Material (via [SekaiShaderStandalone](https://github.com/mos9527/sssekai/blob/main/sssekai_blender_io/assets/SekaiShaderStandalone.blend))
42
+ * Static Mesh
43
+ * Skinned Mesh
44
+ * Armatures (built through GameObject hierarchy)
45
+ * Animations (Skeletal, BlendShape)
46
+
47
+ ## Installation:
48
+ *WIP*
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
sssekai-0.0.1/setup.py ADDED
@@ -0,0 +1,27 @@
1
+ import sys,os
2
+
3
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
4
+ import setuptools, sssekai
5
+
6
+ with open("README.md", "r", encoding='utf-8') as fh:
7
+ long_description = fh.read()
8
+
9
+ setuptools.setup(
10
+ name="sssekai",
11
+ version=sssekai.__version__,
12
+ author="greats3an",
13
+ author_email="greats3an@gmail.com",
14
+ description="",
15
+ long_description=long_description,
16
+ long_description_content_type="text/markdown",
17
+ url="https://github.com/mos9527/sssekai",
18
+ packages=setuptools.find_packages(),
19
+ classifiers=[
20
+ "Programming Language :: Python :: 3",
21
+ "License :: OSI Approved :: Apache Software License",
22
+ "Operating System :: OS Independent",
23
+ ],
24
+ install_requires=["msgpack","pycryptodome","unitypy","wannacri"],
25
+ entry_points={"console_scripts": ["sssekai = sssekai.__main__:__main__"]},
26
+ python_requires=">=3.8",
27
+ )
@@ -0,0 +1,6 @@
1
+ __VERSION_MAJOR__ = 0
2
+ __VERSION_MINOR__ = 0
3
+ __VERSION_PATCH__ = 1
4
+
5
+ __version__ = '%s.%s.%s' % (__VERSION_MAJOR__,__VERSION_MINOR__,__VERSION_PATCH__)
6
+
@@ -0,0 +1,77 @@
1
+ import codecs
2
+ import sys, os
3
+ import argparse
4
+ from sssekai.entrypoint.apidecrypt import main_apidecrypt
5
+ from sssekai.entrypoint.abdecrypt import main_abdecrypt
6
+ from sssekai.entrypoint.mitm import main_mitm
7
+ from sssekai.entrypoint.usmdemux import main_usmdemux
8
+ from sssekai.entrypoint.abcache import main_abcache, DEFAULT_CACHE_DIR
9
+ from sssekai.entrypoint.live2dextract import main_live2dextract
10
+ from sssekai.unity import SEKAI_UNITY_VERSION
11
+ def __main__():
12
+ import coloredlogs
13
+ from logging import basicConfig
14
+ coloredlogs.install(
15
+ level='INFO',
16
+ fmt="%(asctime)s %(name)s [%(levelname).4s] %(message)s",
17
+ isatty=True,
18
+ )
19
+ basicConfig(
20
+ level='INFO', format="[%(levelname).4s] %(name)s %(message)s"
21
+ )
22
+
23
+ parser = argparse.ArgumentParser(description='''SSSekai Proejct SEKAI feat. Hatsune Miku (Android) Modding Tools
24
+ Installation:
25
+ pip install git+https://github.com/mos9527/sssekai
26
+ ''', formatter_class=argparse.RawTextHelpFormatter)
27
+ subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands', help='additional help')
28
+ # apidecrypt
29
+ apidecrypt_parser = subparsers.add_parser('apidecrypt', help='''API crypto dumper
30
+ This crypto applies to:
31
+ - API request/response body dumped by packet sniffer (mitmproxy, wireshark, etc.)
32
+ - AssetBundleInfo (can be found at /sdcard/Android/data/com.hermes.mk.asia/files/data/AssetBundleInfo,or see sssekai.abcache)''')
33
+ apidecrypt_parser.add_argument('infile', type=str, help='input dump file')
34
+ apidecrypt_parser.add_argument('outfile', type=str, help='output json file')
35
+ apidecrypt_parser.set_defaults(func=main_apidecrypt)
36
+ # abdecrypt
37
+ abdecrypt_parser = subparsers.add_parser('abdecrypt', help='''Decrypt Sekai AssetBundle
38
+ These can be found at /sdcard/Android/data/com.hermes.mk.asia/files/data/
39
+ ''')
40
+ abdecrypt_parser.add_argument('indir', type=str, help='input directory')
41
+ abdecrypt_parser.add_argument('outdir', type=str, help='output directory')
42
+ abdecrypt_parser.set_defaults(func=main_abdecrypt)
43
+ # usmdemux
44
+ usmdemux_parser = subparsers.add_parser('usmdemux', help='''Demux Sekai USM Video in a AssetBundle''')
45
+ usmdemux_parser.add_argument('infile', type=str, help='input file')
46
+ usmdemux_parser.add_argument('outdir', type=str, help='output directory')
47
+ usmdemux_parser.set_defaults(func=main_usmdemux)
48
+ # abcache
49
+ abcache_parser = subparsers.add_parser('abcache', help='''Sekai AssetBundle local cache
50
+ Downloads/Updates *ALL* PJSK TW assets to local devices.
51
+ NOTE: The assets can take quite a lot of space (est. 27GB) so be prepared
52
+ NOTE: The AssetBundles cached are NOT OBFUSCATED. They can be used as is by various Unity ripping tools (and sssekai by extension)
53
+ that supports stripped Unity version (should be %s).''' % SEKAI_UNITY_VERSION)
54
+ abcache_parser.add_argument('--cache-dir', type=str, help='cache directory',default=DEFAULT_CACHE_DIR)
55
+ abcache_parser.add_argument('--skip-update',action='store_true',help='skip all updates and use cached assets as is.')
56
+ abcache_parser.add_argument('--open', action='store_true',help='open cache directory. this will skip all updates.')
57
+ abcache_parser.set_defaults(func=main_abcache)
58
+ # live2dextract
59
+ live2dextract_parser = subparsers.add_parser('live2dextract', help='''Extract Sekai Live2D Models in a AssetBundle''')
60
+ live2dextract_parser.add_argument('infile', type=str, help='input file')
61
+ live2dextract_parser.add_argument('outdir', type=str, help='output directory')
62
+ live2dextract_parser.add_argument('--no-anim',action='store_true',help='don\'t extract animation clips')
63
+ live2dextract_parser.set_defaults(func=main_live2dextract)
64
+ # mitm
65
+ mitm_parser = subparsers.add_parser('mitm', help='Run Sekai API MITM proxy (WIP)')
66
+ mitm_parser.set_defaults(func=main_mitm)
67
+ # parse args
68
+ args = parser.parse_args()
69
+ if 'func' in args:
70
+ args.func(args)
71
+ else:
72
+ parser.print_help()
73
+
74
+
75
+ if __name__ == "__main__":
76
+ __main__()
77
+ sys.exit(0)
@@ -0,0 +1,188 @@
1
+ from os import path, remove, makedirs, stat
2
+ from typing import List, Mapping
3
+ from dataclasses import dataclass, asdict
4
+ from requests import get as http_get, Session
5
+ from logging import getLogger
6
+ from json import dump, load
7
+ from pathlib import Path
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ logger = getLogger('sssekai.abcache')
10
+
11
+ from sssekai.crypto.APIManager import decrypt
12
+ from sssekai.crypto.AssetBundle import decrypt_headaer_inplace, SEKAI_AB_MAGIC
13
+ from msgpack import unpackb
14
+ from tqdm import tqdm
15
+ DEFAULT_CACHE_DIR = '~/.sssekai/abcache'
16
+ # 2.6.1
17
+ SEKAI_CDN = 'https://lf16-mkovscdn-sg.bytedgame.com/obj/sf-game-alisg/gdl_app_5245/'
18
+ SEKAI_AB_BASE_PATH = 'AssetBundle/2.6.0/Release/online/'
19
+ SEKAI_AB_INDEX_PATH = SEKAI_AB_BASE_PATH + 'android21/AssetBundleInfo.json'
20
+
21
+ class ThreadpoolDownloader(ThreadPoolExecutor):
22
+ session : Session
23
+ progress : tqdm
24
+
25
+ def download(self, url, fname, length):
26
+ RETRIES = 1
27
+ for _ in range(0,RETRIES):
28
+ try:
29
+ resp = self.session.get(url,stream=True)
30
+ resp.raise_for_status()
31
+ makedirs(path.dirname(fname),exist_ok=True)
32
+ with open(fname, 'wb') as f:
33
+ magic = next(resp.iter_content(4))
34
+ if magic == SEKAI_AB_MAGIC:
35
+ header = next(resp.iter_content(128))
36
+ self.progress.update(128)
37
+ f.write(decrypt_headaer_inplace(bytearray(header)))
38
+ else:
39
+ f.write(magic)
40
+ for chunk in resp.iter_content(65536):
41
+ self.progress.update(len(chunk))
42
+ f.write(chunk)
43
+ return
44
+ except Exception as e:
45
+ logger.error('While downloading %s : %s. Retrying' % (url,e))
46
+ if _ == RETRIES - 1:
47
+ logger.critical('Did not download %s' % url)
48
+ def add_link(self, url, fname, length):
49
+ self.progress.total += length
50
+ return self.submit(self.download, url, fname, length)
51
+ def __init__(self) -> None:
52
+ self.session = Session()
53
+ self.progress = tqdm(bar_format="{desc}: {percentage:.1f}%|{bar}| {n_fmt}/{total_fmt} {rate_fmt} {elapsed}<{remaining}", total=0,unit='B', unit_scale=True, unit_divisor=1024,desc="Downloading")
54
+ super().__init__() # workers = processor count * 5
55
+
56
+ class AbCacheConfig:
57
+ cache_dir : str # absolute path to cache directory
58
+ downloader : ThreadpoolDownloader
59
+ def __init__(self, downloader : ThreadpoolDownloader, cache_dir: str = DEFAULT_CACHE_DIR) -> None:
60
+ self.cache_dir = path.expanduser(cache_dir)
61
+ self.cache_dir = path.abspath(self.cache_dir)
62
+ self.downloader = downloader
63
+
64
+ @dataclass
65
+ class AbCacheEntry(dict):
66
+ bundleName: str
67
+ cacheFileName: str
68
+ cacheDirectoryName: str
69
+ hash: str
70
+ category: str
71
+ crc: int
72
+ fileSize:int
73
+ dependencies : List[str]
74
+ paths : List[str]
75
+ isBuiltin : bool
76
+ md5Hash : str
77
+ downloadPath : str
78
+
79
+ def up_to_date(self, config: AbCacheConfig, other) -> bool:
80
+ fsize = self.get_file_size(config)
81
+ return self.hash == other.hash and self.fileSize <= fsize # fsize may be fileSize + 4
82
+
83
+ def get_file_size(self, config: AbCacheConfig):
84
+ if self.get_file_exists(config):
85
+ return stat(self.get_file_path(config)).st_size
86
+ else:
87
+ return 0
88
+
89
+ def get_file_exists(self, config: AbCacheConfig) -> bool:
90
+ return path.exists(self.get_file_path(config))
91
+
92
+ def get_file_path(self, config: AbCacheConfig) -> str:
93
+ return path.join(config.cache_dir, self.bundleName)
94
+ @dataclass
95
+ class AbCacheIndex(dict):
96
+ version : str
97
+ bundles : Mapping[str, AbCacheEntry]
98
+ def get_bundle_by_abcache_path(self, config: AbCacheConfig, fpath) -> AbCacheEntry | None:
99
+ # "C:\Users\mos9527\.sssekai\abcache\title_screen\anniversary_2nd_bg"
100
+ fpath = Path(fpath)
101
+ cpath = Path(config.cache_dir)
102
+ assert fpath.is_relative_to(cpath), "File provided is not inside the cache"
103
+ relpath = fpath.relative_to(cpath).as_posix()
104
+ return self.bundles.get(relpath,None)
105
+
106
+ class AbCache:
107
+ config : AbCacheConfig
108
+ index : AbCacheIndex
109
+
110
+ @staticmethod
111
+ def download_cache_index() -> AbCacheIndex:
112
+ resp = http_get(
113
+ url=SEKAI_CDN + SEKAI_AB_INDEX_PATH,
114
+ headers={
115
+ 'Accept-Encoding': 'deflate, gzip'
116
+ }
117
+ )
118
+ resp.raise_for_status()
119
+ data = decrypt(resp.content)
120
+ data = unpackb(data)
121
+ cache = AbCacheIndex(**data)
122
+ for k,v in cache.bundles.items():
123
+ cache.bundles[k] = AbCacheEntry(**v)
124
+ return cache
125
+
126
+ def update_cahce_entry(self, entry : AbCacheEntry, new_entry : AbCacheEntry = None):
127
+ if new_entry:
128
+ entry = new_entry
129
+ return self.config.downloader.add_link(
130
+ SEKAI_CDN + SEKAI_AB_BASE_PATH + entry.downloadPath,
131
+ entry.get_file_path(self.config),
132
+ entry.fileSize
133
+ )
134
+
135
+ def update_cahce_index(self):
136
+ logger.info('Downloading AssetBundleInfo.json')
137
+ dl = AbCache.download_cache_index()
138
+ all_keys = sorted(list({k for k in dl.bundles.keys()}.union({k for k in self.index.bundles.keys()})))
139
+ update_count = 0
140
+ for k in all_keys:
141
+ if k in dl.bundles and k in self.index.bundles:
142
+ # update
143
+ if not dl.bundles[k].up_to_date(self.config, self.index.bundles[k]):
144
+ logger.info('Updating bundle %s', k)
145
+ self.update_cahce_entry(self.index.bundles[k], dl.bundles[k])
146
+ update_count+=1
147
+ else:
148
+ logger.debug('Bundle %s is up to date' % k)
149
+ pass
150
+ elif k in dl.bundles:
151
+ # append
152
+ logger.info('Adding bundle %s', k)
153
+ self.index.bundles[k] = dl.bundles[k]
154
+ self.update_cahce_entry(self.index.bundles[k])
155
+ update_count+=1
156
+ else:
157
+ # removal
158
+ logger.info('Removing bundle %s', k)
159
+ if path.exists(self.index.bundles[k].get_file_path(self.config)):
160
+ remove(self.index.bundles[k].get_file_path(self.config))
161
+ del self.index.bundles[k]
162
+ logger.info('Saving AssetBundle index')
163
+ self.save()
164
+ logger.info('...Saved. Need %d updates' % update_count)
165
+ self.config.downloader.shutdown(wait=True)
166
+ logger.info('AssetBundles are now up-to-date')
167
+
168
+ def __init__(self, config : AbCacheConfig) -> None:
169
+ self.config = config
170
+ makedirs(self.config.cache_dir,exist_ok=True)
171
+ try:
172
+ self.load()
173
+ except Exception as e:
174
+ logger.warning('Failed to load cache index. Creating new one. (%s)' % e)
175
+ self.index = AbCacheIndex(None, {})
176
+ self.save()
177
+
178
+ def save(self):
179
+ with open(path.join(self.config.cache_dir, 'abindex.json'),'w',encoding='utf-8') as f:
180
+ dump(asdict(self.index), f, indent=4, ensure_ascii=False)
181
+ logger.info("Saved %d entries to cache index" % len(self.index.bundles))
182
+
183
+ def load(self):
184
+ with open(path.join(self.config.cache_dir, 'abindex.json'),'r',encoding='utf-8') as f:
185
+ self.index = AbCacheIndex(**load(f))
186
+ for k,v in self.index.bundles.items():
187
+ self.index.bundles[k] = AbCacheEntry(**v)
188
+ logger.info("Loaded %d entries from cache index" % len(self.index.bundles))
@@ -0,0 +1,24 @@
1
+ from Crypto.Cipher import AES
2
+ def PKCS7_pad(data : bytes,bs) -> bytes:
3
+ size = (bs - len(data) % bs)
4
+ return data + bytes([size] * size)
5
+
6
+ def PKCS7_unpad(data : bytes,bs) -> bytes:
7
+ pad = data[-1]
8
+ return data[:-pad]
9
+
10
+ def decrypt_aes_cbc(data : bytes, key, iv, unpad=PKCS7_unpad) -> bytes:
11
+ cipher = AES.new(key, AES.MODE_CBC, iv)
12
+ return unpad(cipher.decrypt(data), cipher.block_size)
13
+
14
+ def encrypt_aes_cbc(data : bytes, key, iv, pad=PKCS7_pad) -> bytes:
15
+ cipher = AES.new(key, AES.MODE_CBC, iv)
16
+ return cipher.encrypt(pad(data, cipher.block_size))
17
+
18
+ SEKAI_APIMANAGER_KEY = b"g2fcC0ZczN9MTJ61"
19
+ SEKAI_APIMANAGER_IV = b"msx3IV0i9XE5uYZ1"
20
+ def encrypt(data : bytes) -> bytes:
21
+ return encrypt_aes_cbc(data,SEKAI_APIMANAGER_KEY,SEKAI_APIMANAGER_IV, PKCS7_pad)
22
+
23
+ def decrypt(data : bytes) -> bytes:
24
+ return decrypt_aes_cbc(data,SEKAI_APIMANAGER_KEY,SEKAI_APIMANAGER_IV, PKCS7_unpad)
@@ -0,0 +1,32 @@
1
+ from io import BytesIO
2
+ from typing import BinaryIO
3
+
4
+ SEKAI_AB_MAGIC = b'\x10\x00\x00\x00'
5
+ def decrypt_headaer_inplace(header : bytearray):
6
+ assert len(header) == 128
7
+ for i in range(0,128,8):
8
+ for j in range(5):
9
+ header[i + j] = ~header[i + j] & 0xff
10
+ return header
11
+
12
+ def decrypt(fin : BinaryIO, fout : BinaryIO = None) -> None:
13
+ if not fout:
14
+ fout = BytesIO()
15
+ magic = fin.read(4)
16
+ if magic == SEKAI_AB_MAGIC:
17
+ fout.write(decrypt_headaer_inplace(bytearray(fin.read(128))))
18
+ while (block := fin.read(65536)):
19
+ fout.write(block)
20
+ else:
21
+ fin.seek(0)
22
+ while (block := fin.read(65536)):
23
+ fout.write(block)
24
+ return fout
25
+
26
+ def encrypt(fin, fout):
27
+ raise NotImplementedError
28
+
29
+ def has_magic(fin : BinaryIO) -> bool:
30
+ magic = fin.read(4)
31
+ fin.seek(0)
32
+ return magic == SEKAI_AB_MAGIC
File without changes
File without changes
@@ -0,0 +1,15 @@
1
+ from sssekai.abcache import AbCache, AbCacheConfig,ThreadpoolDownloader, logger, DEFAULT_CACHE_DIR
2
+ def main_abcache(args):
3
+ downloader = ThreadpoolDownloader()
4
+ cache_dir = args.cache_dir or DEFAULT_CACHE_DIR
5
+ config = AbCacheConfig(downloader, cache_dir)
6
+ if args.open:
7
+ from os import startfile
8
+ startfile(config.cache_dir)
9
+ return
10
+ cache = AbCache(config)
11
+ if not args.skip_update:
12
+ cache.update_cahce_index()
13
+ downloader.shutdown()
14
+ cache.save()
15
+ logger.info('AssetBundle cache is now ready. Visit: %s' % cache_dir)
@@ -0,0 +1,9 @@
1
+ import os
2
+ def main_abdecrypt(args):
3
+ from sssekai.crypto.AssetBundle import decrypt
4
+ tree = os.walk(args.indir)
5
+ for root, dirs, files in tree:
6
+ for fname in files:
7
+ file = os.path.join(root,fname)
8
+ if (os.path.isfile(file)):
9
+ decrypt(open(file,'rb'), open(os.path.join(args.outdir, fname),'wb'))
@@ -0,0 +1,9 @@
1
+ import os
2
+ def main_apidecrypt(args):
3
+ from sssekai.crypto.APIManager import decrypt
4
+ import json, msgpack
5
+ data = open(args.infile,'rb').read()
6
+ with open(args.outfile,'w',encoding='utf-8') as fout:
7
+ plain = decrypt(data)
8
+ msg = msgpack.unpackb(plain)
9
+ json.dump(msg, fout, indent=4, ensure_ascii=False)