azlassets 3.3.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 nobbyfix
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: azlassets
3
+ Version: 3.3.0
4
+ Summary: Azur Lane asset downloader and extractor
5
+ Author: nobbyfix
6
+ Project-URL: homepage, https://github.com/nobbyfix/AzurLane-AssetDownloader
7
+ Project-URL: repository, https://github.com/nobbyfix/AzurLane-AssetDownloader.git
8
+ Project-URL: issues, https://github.com/nobbyfix/AzurLane-AssetDownloader/issues
9
+ Project-URL: changelog, https://github.com/nobbyfix/AzurLane-AssetDownloader/blob/master/CHANGELOG.md
10
+ Project-URL: download, https://github.com/nobbyfix/AzurLane-AssetDownloader/releases/latest
11
+ Keywords: azurlane,azur-lane
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: 3.15
25
+ Classifier: Topic :: Games/Entertainment
26
+ Requires-Python: >=3.11
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: UnityPy>=1.10.18
30
+ Requires-Dist: PyYAML
31
+ Requires-Dist: Pillow
32
+ Requires-Dist: protobuf
33
+ Requires-Dist: aiofile
34
+ Requires-Dist: aiohttp[speedups]
35
+ Provides-Extra: dev
36
+ Requires-Dist: pre-commit; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # Azur Lane Asset Downloader and Extractor
40
+ This tool automatically downloads the newest assets directly from the game's CDN servers and allows extraction of Texture2D files as PNG images.
41
+
42
+ ## Upgrade Notice
43
+ ### From 2.x / no version number to 3.x+
44
+ When upgrading from versions 2.x or with no version number, the project has to be newly set up. To retain all current data the following folders can be copied to the new folder of the project:
45
+ - `config`: Only user_config.yml is required, the rest can be deleted.
46
+ - `ClientAssets` or directory set in `asset-directory` of the config: Contains all currently downloaded assets, version information und update logs used for extraction. Highly recommend to transfer to the new project folder.
47
+
48
+ ## Setup
49
+ Before installation, Python 3.11 or newer needs to be available on the system. It is recommended to set the project up using [venv](https://docs.python.org/3/tutorial/venv.html) or a similar virtual environment manager. The project can be installed using pip:
50
+
51
+ ```
52
+ pip install azlassets
53
+ ```
54
+
55
+ Alternatively, to install the newest version from the repository (requires git on the system):
56
+ ```
57
+ pip install git+https://github.com/nobbyfix/AzurLane-AssetDownloader.git
58
+ ```
59
+
60
+ ## Usage
61
+ There are three scripts to manage the assets:
62
+ - `obb_apk_import`: Importing assets from obb/apk/xapk files
63
+ - `downlader`: Downloading assets from the game server
64
+ - `extractor`: Extract PNGs from the assets
65
+
66
+ These can be executed using `py -m <scriptname>` on Windows or `python3 -m <scriptname>` on Linux/macOS (will be shortened to `py[thon3]` going forward, use the appropriate version for your system). Detailed usage will be explained in the following sections.
67
+
68
+ ### 1. Import files from xapk/apk/obb
69
+ While this is *not necessary*, this step is **recommended** if you want all game assets available and not spam the game update server with errors of missing files on the first download.
70
+
71
+ The `obb_apk_import.py` supports all game clients (EN, JP, CN, KR, TW) and multiple forms of importing the assets. The recommended and easiest way is by downloading the `.xapk` from one of many Google Play Store app distributors (like APKMirror or APKPure). You can find them by searching for the package name, which are as follows:
72
+ - EN: com.YoStarEN.AzurLane
73
+ - JP: com.YoStarJP.AzurLane
74
+ - KR: kr.txwy.and.blhx
75
+ - TW: com.hkmanjuu.azurlane.gp
76
+
77
+ Alternatively if you already have the game installed, for example on emulators, you can copy the obb file onto your system and use it instead of the xapk. On Android it can be found in the folder `/storage/emulated/0/Android/obb/[PACKAGE_NAME]/`.
78
+
79
+ Since the CN client is not distributed through the Google Play Store, there is no xapk/obb file for it, but you can find the android download link on the [website](https://game.bilibili.com/blhx/) which will download an apk file (not xapk like the others). Alternatively, the APK is installed in the folder `/data/app/com.bilibili.azurlane-1/` on android (Note: Root access is required to access this folder).
80
+
81
+ You can then execute the script by passing it the filepath to the xapk/apk/obb:
82
+ ```
83
+ py[thon3] -m obb_apk_import [FILEPATH]
84
+ ```
85
+
86
+ ### 2. Settings
87
+ The `config/user_config.yml` file provides a few settings to filter which files will be downloaded (and later also extracted). The options `download-folder-listtype` and `extract-folder-listtype` can be set to either "blacklist" or "whitelist". Depending on this it will filter by the top-level folder names (subfolders are not supported) or top-level filenames (files inside top-level folders or lower cannot be filtered) set in `download-folder-list` and `extract-folder-list`. This allows to cut down the download and extraction times by skipping unneeded assets.
88
+
89
+ ### 3. Download new updates from the game
90
+ All assets normally distributed via the in-app downloader can be downloaded by simply executing:
91
+ ```
92
+ py[thon3] -m downloader [CLIENT]
93
+ ```
94
+ where `CLIENT` has to be either EN, CN, JP, KR or TW. You can check which files have been downloaded or deleted using the difflog files in `ClientAssets/[CLIENT]/difflog`.
95
+
96
+ ### 4. Extract all new and changed files
97
+ The asset extraction script supports extraction of all newly downloaded files and single asset bundles. The newly downloaded assets can be extracted by executing:
98
+ ```
99
+ py[thon3] -m extractor [CLIENT]
100
+ ```
101
+ where `CLIENT` is again one of EN, CN, JP, KR or TW. The extracted images will then be saved in `ClientExtract/[CLIENT]/` Since only Texture2D assets are exported, it's not desired to try to export from all assetbundles (See [settings section](#2-settings)).
102
+
103
+ A single assetbundle can be extracted by passing the filepath to the script:
104
+ ```
105
+ py[thon3] -m extractor -f [FILEPATH]
106
+ ```
107
+
108
+ ### 5. Enjoy the files
@@ -0,0 +1,70 @@
1
+ # Azur Lane Asset Downloader and Extractor
2
+ This tool automatically downloads the newest assets directly from the game's CDN servers and allows extraction of Texture2D files as PNG images.
3
+
4
+ ## Upgrade Notice
5
+ ### From 2.x / no version number to 3.x+
6
+ When upgrading from versions 2.x or with no version number, the project has to be newly set up. To retain all current data the following folders can be copied to the new folder of the project:
7
+ - `config`: Only user_config.yml is required, the rest can be deleted.
8
+ - `ClientAssets` or directory set in `asset-directory` of the config: Contains all currently downloaded assets, version information und update logs used for extraction. Highly recommend to transfer to the new project folder.
9
+
10
+ ## Setup
11
+ Before installation, Python 3.11 or newer needs to be available on the system. It is recommended to set the project up using [venv](https://docs.python.org/3/tutorial/venv.html) or a similar virtual environment manager. The project can be installed using pip:
12
+
13
+ ```
14
+ pip install azlassets
15
+ ```
16
+
17
+ Alternatively, to install the newest version from the repository (requires git on the system):
18
+ ```
19
+ pip install git+https://github.com/nobbyfix/AzurLane-AssetDownloader.git
20
+ ```
21
+
22
+ ## Usage
23
+ There are three scripts to manage the assets:
24
+ - `obb_apk_import`: Importing assets from obb/apk/xapk files
25
+ - `downlader`: Downloading assets from the game server
26
+ - `extractor`: Extract PNGs from the assets
27
+
28
+ These can be executed using `py -m <scriptname>` on Windows or `python3 -m <scriptname>` on Linux/macOS (will be shortened to `py[thon3]` going forward, use the appropriate version for your system). Detailed usage will be explained in the following sections.
29
+
30
+ ### 1. Import files from xapk/apk/obb
31
+ While this is *not necessary*, this step is **recommended** if you want all game assets available and not spam the game update server with errors of missing files on the first download.
32
+
33
+ The `obb_apk_import.py` supports all game clients (EN, JP, CN, KR, TW) and multiple forms of importing the assets. The recommended and easiest way is by downloading the `.xapk` from one of many Google Play Store app distributors (like APKMirror or APKPure). You can find them by searching for the package name, which are as follows:
34
+ - EN: com.YoStarEN.AzurLane
35
+ - JP: com.YoStarJP.AzurLane
36
+ - KR: kr.txwy.and.blhx
37
+ - TW: com.hkmanjuu.azurlane.gp
38
+
39
+ Alternatively if you already have the game installed, for example on emulators, you can copy the obb file onto your system and use it instead of the xapk. On Android it can be found in the folder `/storage/emulated/0/Android/obb/[PACKAGE_NAME]/`.
40
+
41
+ Since the CN client is not distributed through the Google Play Store, there is no xapk/obb file for it, but you can find the android download link on the [website](https://game.bilibili.com/blhx/) which will download an apk file (not xapk like the others). Alternatively, the APK is installed in the folder `/data/app/com.bilibili.azurlane-1/` on android (Note: Root access is required to access this folder).
42
+
43
+ You can then execute the script by passing it the filepath to the xapk/apk/obb:
44
+ ```
45
+ py[thon3] -m obb_apk_import [FILEPATH]
46
+ ```
47
+
48
+ ### 2. Settings
49
+ The `config/user_config.yml` file provides a few settings to filter which files will be downloaded (and later also extracted). The options `download-folder-listtype` and `extract-folder-listtype` can be set to either "blacklist" or "whitelist". Depending on this it will filter by the top-level folder names (subfolders are not supported) or top-level filenames (files inside top-level folders or lower cannot be filtered) set in `download-folder-list` and `extract-folder-list`. This allows to cut down the download and extraction times by skipping unneeded assets.
50
+
51
+ ### 3. Download new updates from the game
52
+ All assets normally distributed via the in-app downloader can be downloaded by simply executing:
53
+ ```
54
+ py[thon3] -m downloader [CLIENT]
55
+ ```
56
+ where `CLIENT` has to be either EN, CN, JP, KR or TW. You can check which files have been downloaded or deleted using the difflog files in `ClientAssets/[CLIENT]/difflog`.
57
+
58
+ ### 4. Extract all new and changed files
59
+ The asset extraction script supports extraction of all newly downloaded files and single asset bundles. The newly downloaded assets can be extracted by executing:
60
+ ```
61
+ py[thon3] -m extractor [CLIENT]
62
+ ```
63
+ where `CLIENT` is again one of EN, CN, JP, KR or TW. The extracted images will then be saved in `ClientExtract/[CLIENT]/` Since only Texture2D assets are exported, it's not desired to try to export from all assetbundles (See [settings section](#2-settings)).
64
+
65
+ A single assetbundle can be extracted by passing the filepath to the script:
66
+ ```
67
+ py[thon3] -m extractor -f [FILEPATH]
68
+ ```
69
+
70
+ ### 5. Enjoy the files
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "azlassets"
7
+ authors = [{ name = "nobbyfix" }]
8
+ description ="Azur Lane asset downloader and extractor"
9
+ readme = "README.md"
10
+ license-files = ["LICENSE"]
11
+ requires-python = ">=3.11"
12
+ dynamic = ["version"]
13
+ dependencies = [
14
+ "UnityPy >= 1.10.18",
15
+ "PyYAML",
16
+ "Pillow",
17
+ "protobuf",
18
+ "aiofile",
19
+ "aiohttp[speedups]"
20
+ ]
21
+ keywords = [
22
+ "azurlane",
23
+ "azur-lane"
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 5 - Production/Stable",
27
+ "Environment :: Console",
28
+ "Intended Audience :: Developers",
29
+ "Intended Audience :: End Users/Desktop",
30
+ "Operating System :: OS Independent",
31
+ "Programming Language :: Python",
32
+ "Programming Language :: Python :: 3",
33
+ "Programming Language :: Python :: 3 :: Only",
34
+ "Programming Language :: Python :: 3.11",
35
+ "Programming Language :: Python :: 3.12",
36
+ "Programming Language :: Python :: 3.13",
37
+ "Programming Language :: Python :: 3.14",
38
+ "Programming Language :: Python :: 3.15",
39
+ "Topic :: Games/Entertainment"
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ dev = ["pre-commit"]
44
+
45
+ [project.urls]
46
+ homepage = "https://github.com/nobbyfix/AzurLane-AssetDownloader"
47
+ repository = "https://github.com/nobbyfix/AzurLane-AssetDownloader.git"
48
+ issues = "https://github.com/nobbyfix/AzurLane-AssetDownloader/issues"
49
+ changelog = "https://github.com/nobbyfix/AzurLane-AssetDownloader/blob/master/CHANGELOG.md"
50
+ download = "https://github.com/nobbyfix/AzurLane-AssetDownloader/releases/latest"
51
+
52
+ [tool.setuptools.dynamic]
53
+ version = { attr = "azlassets.__version__" }
54
+
55
+ [tool.setuptools.package-data]
56
+ "azlassets.config" = ["*.json", "*.yml"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "3.3.0" # x-release-please-version
@@ -0,0 +1,191 @@
1
+ from enum import Enum
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ from typing import Self
5
+
6
+
7
+ CompareType = Enum('CompareType', 'New Changed Unchanged Deleted')
8
+ DownloadType = Enum('DownloadType', 'NoChange Removed Success Failed ForDeletionNoChange')
9
+
10
+ class VersionType(Enum):
11
+ __hash2member_map__: dict[str, Self] = {}
12
+ hashname: str
13
+ """Hash name used on the version result returned by the game server."""
14
+ suffix: str
15
+ """Suffix used on version and hash files."""
16
+
17
+ AZL = (1, "azhash", "")
18
+ CV = (2, "cvhash", "cv")
19
+ L2D = (3, "l2dhash", "live2d")
20
+ PIC = (4, "pichash", "pic")
21
+ BGM = (5, "bgmhash", "bgm")
22
+ CIPHER = (6, "cipherhash", "cipher")
23
+ MANGA = (7, "mangahash", "manga")
24
+ PAINTING = (8, "paintinghash", "painting")
25
+ DORM = (9, "dormhash", "dorm")
26
+ MAP = (10, "maphash", "map")
27
+
28
+
29
+ def __init__(self, _, hashname, suffix) -> None:
30
+ # add attributes to enum objects
31
+ self.hashname = hashname
32
+ self.suffix = suffix
33
+ # add enum objects to member maps
34
+ self.__hash2member_map__[hashname] = self
35
+
36
+ def __str__(self) -> str:
37
+ return self.name.lower()
38
+
39
+ @property
40
+ def version_filename(self) -> str:
41
+ """
42
+ Full version filename using the suffix.
43
+ """
44
+ suffix = self.suffix
45
+ if suffix: suffix = "-"+suffix
46
+ return f"version{suffix}.txt"
47
+
48
+ @property
49
+ def hashes_filename(self) -> str:
50
+ """
51
+ Full hashes filename using the suffix.
52
+ """
53
+ suffix = self.suffix
54
+ if suffix: suffix = "-"+suffix
55
+ return f"hashes{suffix}.csv"
56
+
57
+ @classmethod
58
+ def from_hashname(cls, hashname: str) -> Self | None:
59
+ """
60
+ Returns a VersionType member with matching *hashname* if match exists, otherwise None.
61
+ """
62
+ return cls.__hash2member_map__.get(hashname)
63
+
64
+
65
+ class AbstractClient(Enum):
66
+ active: bool
67
+ locale_code: str
68
+ package_name: str
69
+
70
+ def __new__(cls, value, active, locale, package_name):
71
+ # this should be done differently, but i am too lazy to do that now
72
+ # TODO: change it
73
+ if not hasattr(cls, "package_names"):
74
+ cls.package_names = {}
75
+
76
+ obj = object.__new__(cls)
77
+ obj._value_ = value
78
+ obj.active = active
79
+ obj.locale_code = locale
80
+ obj.package_name = package_name
81
+ cls.package_names[package_name] = obj
82
+ return obj
83
+
84
+ @classmethod
85
+ def from_package_name(cls, package_name) -> Self | None:
86
+ return cls.package_names.get(package_name)
87
+
88
+
89
+ class Client(AbstractClient):
90
+ EN = (1, True, 'en-US', 'com.YoStarEN.AzurLane')
91
+ JP = (2, True, 'ja-JP', 'com.YoStarJP.AzurLane')
92
+ CN = (3, True, 'zh-CN', '')
93
+ KR = (4, True, 'ko-KR', 'kr.txwy.and.blhx')
94
+ TW = (5, True, 'zh-TW', 'com.hkmanjuu.azurlane.gp')
95
+
96
+
97
+ @dataclass
98
+ class HashRow:
99
+ filepath: str
100
+ size: int
101
+ md5hash: str
102
+
103
+ @dataclass
104
+ class CompareResult:
105
+ current_hash: HashRow | None
106
+ new_hash: HashRow | None
107
+ compare_type: CompareType
108
+
109
+ @dataclass
110
+ class VersionResult:
111
+ version: str
112
+ vhash: str
113
+ rawstring: str
114
+ version_type: VersionType
115
+
116
+ @dataclass
117
+ class BundlePath:
118
+ full: Path
119
+ inner: str
120
+
121
+ @staticmethod
122
+ def construct(parentdir: Path, inner: Path | str) -> "BundlePath":
123
+ fullpath = Path(parentdir, inner)
124
+ return BundlePath(fullpath, str(inner))
125
+
126
+ def __hash__(self):
127
+ return hash(self.inner)
128
+
129
+ @dataclass
130
+ class UpdateResult:
131
+ compare_result: CompareResult
132
+ download_type: DownloadType
133
+ path: BundlePath
134
+
135
+ @dataclass
136
+ class UserConfig:
137
+ useragent: str
138
+ download_isblacklist: bool
139
+ download_filter: list
140
+ extract_isblacklist: bool
141
+ extract_filter: list
142
+ asset_directory: Path
143
+ extract_directory: Path
144
+
145
+ @dataclass
146
+ class ClientConfig:
147
+ gateip: str
148
+ gateport: int
149
+ cdnurl: str
150
+
151
+
152
+ # stolen from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
153
+ def printProgressBar(iteration, total, prefix = '', suffix = 'Complete', decimals = 1, length = 50, fill = '█', printEnd = "\r", details_unit = None):
154
+ """
155
+ Call in a loop to create terminal progress bar
156
+ @params:
157
+ iteration - Required : current iteration (Int)
158
+ total - Required : total iterations (Int)
159
+ prefix - Optional : prefix string (Str)
160
+ suffix - Optional : suffix string (Str)
161
+ decimals - Optional : positive number of decimals in percent complete (Int)
162
+ length - Optional : character length of bar (Int)
163
+ fill - Optional : bar fill character (Str)
164
+ printEnd - Optional : end character (e.g. "\r", "\r\n") (Str)
165
+ """
166
+ percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
167
+ filledLength = int(length * iteration // total)
168
+ progress = fill * filledLength + '-' * (length - filledLength)
169
+ details = f" [{iteration}/{total} {details_unit}]" if details_unit else ""
170
+ print(f'\r{prefix} |{progress}| {percent}% {suffix}{details}', end = printEnd)
171
+ # Print New Line on Complete
172
+ if iteration == total:
173
+ print()
174
+
175
+ # simplified progress bar class with only the useful stuff i use
176
+ class ProgressBar():
177
+ def __init__(self, total: int, prefix: str, suffix: str = "Complete", iterstart: int = 0, details_unit: str | None = None, print_on_init: bool = True):
178
+ self.iteration = iterstart
179
+ self.total = total
180
+ self.prefix = prefix
181
+ self.suffix = suffix
182
+ self.details_unit = details_unit
183
+ if print_on_init:
184
+ printProgressBar(iterstart, total, prefix, suffix, details_unit=details_unit)
185
+
186
+ def update(self, iteration: int | None = None):
187
+ if iteration:
188
+ self.iteration = iteration
189
+ else:
190
+ self.iteration += 1
191
+ printProgressBar(self.iteration, self.total, self.prefix, self.suffix, details_unit=self.details_unit)
@@ -0,0 +1,27 @@
1
+ {
2
+ "EN": {
3
+ "gateip": "blhxusgate.yo-star.com",
4
+ "gateport": 80,
5
+ "cdnurl": "https://blhxusstatic.yo-star.com"
6
+ },
7
+ "CN": {
8
+ "gateip": "line1-login-bili-blhx.bilibiligame.net",
9
+ "gateport": 80,
10
+ "cdnurl": "https://line3-patch-blhx.bilibiligame.net"
11
+ },
12
+ "JP": {
13
+ "gateip": "blhxjploginapi.azurlane.jp",
14
+ "gateport": 80,
15
+ "cdnurl": "https://blhxstatic.yo-star.com"
16
+ },
17
+ "KR": {
18
+ "gateip": "bl-kr-gate.xdg.com",
19
+ "gateport": 80,
20
+ "cdnurl": "http://blcdn.imtxwy.com"
21
+ },
22
+ "TW": {
23
+ "gateip": "prod-all-login.azurlane.tw",
24
+ "gateport": 10080,
25
+ "cdnurl": "http://blhxstatic.azurlane.tw"
26
+ }
27
+ }
@@ -0,0 +1,61 @@
1
+ asset-directory: ClientAssets
2
+ extract-directory: ClientExtract
3
+ # set to blacklist or whitelist
4
+ download-folder-listtype: blacklist
5
+ extract-folder-listtype: whitelist
6
+ download-folder-list: []
7
+ extract-folder-list:
8
+ - activitybanner
9
+ - aircrafticon
10
+ - backyardtheme
11
+ - battlescore
12
+ - bg
13
+ - boxprefab
14
+ - chargeicon
15
+ - clutter
16
+ - collectionfileillustration
17
+ - collectionfiletitle
18
+ - commanderhrz
19
+ - commandericon
20
+ - commanderskillicon
21
+ - commandertalenticon
22
+ - commonbg
23
+ - crusingwindow
24
+ - emblem
25
+ - enemies
26
+ - equips
27
+ - eventtype
28
+ - extra_page
29
+ - furnitureicon
30
+ - gallerycard
31
+ - guildnode
32
+ - guildboss
33
+ - guildevent
34
+ - guildmission
35
+ - helpbg
36
+ - herohrzicon
37
+ - icondesc
38
+ - iconframe
39
+ - independenttex
40
+ - levelmap
41
+ - loadingbg
42
+ - lotterybg
43
+ - mangapic
44
+ - medal
45
+ - memoryicon
46
+ - metaship
47
+ - musiccover
48
+ - newshipbg
49
+ - newyearskinshowpage
50
+ - painting
51
+ - prints
52
+ - props
53
+ - qicon
54
+ - shipmodels
55
+ - shiprarity
56
+ - shipyardicon
57
+ - skillicon
58
+ - squareicon
59
+ - strategyicon
60
+ - updatebg
61
+ useragent: ''
@@ -0,0 +1,62 @@
1
+ import sys
2
+ import json
3
+ import yaml
4
+ from shutil import copy
5
+ from pathlib import Path
6
+ from importlib.resources import files
7
+
8
+ from .classes import Client, UserConfig, ClientConfig
9
+
10
+
11
+ # package-incuded filepaths
12
+ CONFIG_DATA_PATH = files("azlassets").joinpath("config")
13
+ YAML_TEMPLATE_PATH = CONFIG_DATA_PATH.joinpath("user_config_template.yml")
14
+ CLIENT_CONFIG_PATH = CONFIG_DATA_PATH.joinpath("client_config.json")
15
+
16
+ # cwd-relative filepaths
17
+ YAML_CONFIG_PATH = Path("config") / "user_config.yml"
18
+
19
+
20
+ def load_user_config() -> UserConfig:
21
+ if not YAML_CONFIG_PATH.exists():
22
+ print("Userconfig does not exist. A new one will be created.")
23
+ print("Note that the useragent is empty and it is advised to set one.")
24
+ YAML_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
25
+ copy(YAML_TEMPLATE_PATH, YAML_CONFIG_PATH)
26
+ return load_user_config()
27
+
28
+ with open(YAML_CONFIG_PATH, 'r', encoding='utf8') as file:
29
+ yamlconfig = yaml.safe_load(file)
30
+
31
+ try:
32
+ userconfig = UserConfig(
33
+ useragent=yamlconfig['useragent'],
34
+ download_isblacklist=yamlconfig['download-folder-listtype'] == 'blacklist',
35
+ download_filter=yamlconfig['download-folder-list'],
36
+ extract_isblacklist=yamlconfig['extract-folder-listtype'] == 'blacklist',
37
+ extract_filter=yamlconfig['extract-folder-list'],
38
+ asset_directory=yamlconfig['asset-directory'],
39
+ extract_directory=yamlconfig['extract-directory'],
40
+ )
41
+ except KeyError:
42
+ print("There is an error inside the userconfig file. Delete it or change the wrong values.")
43
+ sys.exit(1)
44
+
45
+ return userconfig
46
+
47
+
48
+ def load_client_config(client: Client) -> ClientConfig:
49
+ with open(CLIENT_CONFIG_PATH, 'r', encoding='utf8') as f:
50
+ configdata = json.load(f)
51
+
52
+ if not client.name in configdata:
53
+ raise NotImplementedError(f'Client {client.name} has not been configured yet.')
54
+
55
+ config = configdata[client.name]
56
+ try:
57
+ clientconfig = ClientConfig(config['gateip'], config['gateport'], config['cdnurl'])
58
+ except KeyError:
59
+ print("The clientconfig has been wrongly configured.")
60
+ sys.exit(1)
61
+
62
+ return clientconfig
@@ -0,0 +1,61 @@
1
+ import aiohttp
2
+ import aiofile
3
+ import traceback
4
+ from pathlib import Path
5
+
6
+ from .classes import VersionResult
7
+
8
+
9
+ class AzurlaneAsyncDownloader(aiohttp.ClientSession):
10
+ def __init__(self, cdn_url: str, useragent: str):
11
+ base_url = f"{cdn_url}/android/"
12
+ limited_tcp_connector = aiohttp.TCPConnector(limit_per_host=6)
13
+ super().__init__(base_url=base_url, headers={"user-agent": useragent}, connector=limited_tcp_connector)
14
+
15
+ async def get_hashes(self, versionhash: str) -> aiohttp.ClientResponse:
16
+ return await self.get(f"hash/{versionhash}")
17
+
18
+ async def get_asset(self, filehash: str) -> aiohttp.ClientResponse:
19
+ return await self.get(f"resource/{filehash}")
20
+
21
+ async def download_hashes(self, version_result: VersionResult) -> str | None:
22
+ try:
23
+ async with await self.get_hashes(version_result.rawstring) as response:
24
+ response: aiohttp.ClientResponse
25
+ response.raise_for_status() # raises error on bad HTTP status
26
+
27
+ hashes = await response.text()
28
+ return hashes
29
+
30
+ except Exception as e:
31
+ print(f"ERROR: An unexpected error occured while downloading '{version_result.version_type.name}' hashfile.")
32
+ traceback.print_exception(type(e), e, e.__traceback__)
33
+ return
34
+
35
+ async def download_asset(self, filehash: str, save_destination: Path, expected_file_size: int) -> bool:
36
+ """
37
+ Downloads the requested file using the session and saves it to 'save_destination' on disk.
38
+
39
+ Returns `True` if the operation was successful, otherwise `False`.
40
+ """
41
+ try:
42
+ async with await self.get_asset(filehash) as response:
43
+ response: aiohttp.ClientResponse
44
+ response.raise_for_status() # raises error on bad HTTP status
45
+
46
+ # reject response if response size doesn't match expected size
47
+ response_size = response.content_length
48
+ if expected_file_size != response_size:
49
+ print(f"ERROR: Received asset '{filehash}' with target '{save_destination}' has wrong size ({response_size}/{expected_file_size}).")
50
+ return False
51
+
52
+ save_destination.parent.mkdir(parents=True, exist_ok=True)
53
+ async with aiofile.async_open(save_destination, "wb") as file:
54
+ async for chunk in response.content.iter_chunked(1024*16): # no idea what chuck size is best
55
+ await file.write(chunk)
56
+
57
+ return True
58
+ except Exception as e:
59
+ print(f"ERROR: An unexpected error occured while downloading '{filehash}' to '{save_destination}'.")
60
+ traceback.print_exception(type(e), e, e.__traceback__)
61
+ return False