forked from Deathemonic/BA-AD
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c546465
Showing
7 changed files
with
475 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Auto detect text files and perform LF normalization | ||
* text=auto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
extracted/ | ||
raw/ | ||
*.apk | ||
lib/ | ||
update_master.py | ||
|
||
# Distribution / packaging | ||
.Python | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
MANIFEST | ||
|
||
# PyInstaller | ||
# Usually these files are written by a python script from a template | ||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
.tox/ | ||
.coverage | ||
.coverage.* | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
*.cover | ||
.hypothesis/ | ||
.pytest_cache/ | ||
|
||
# Translations | ||
*.mo | ||
*.pot | ||
|
||
# Django stuff: | ||
*.log | ||
local_settings.py | ||
db.sqlite3 | ||
|
||
# Flask stuff: | ||
instance/ | ||
.webassets-cache | ||
|
||
# Scrapy stuff: | ||
.scrapy | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# PyBuilder | ||
target/ | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
|
||
# pyenv | ||
.python-version | ||
|
||
# celery beat schedule file | ||
celerybeat-schedule | ||
|
||
# SageMath parsed files | ||
*.sage.py | ||
|
||
# Environments | ||
.env | ||
.venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
env.bak/ | ||
venv.bak/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
.spyproject | ||
|
||
# Rope project settings | ||
.ropeproject | ||
|
||
# mkdocs documentation | ||
/site | ||
|
||
# mypy | ||
.mypy_cache/ | ||
assets/asset_list.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import os | ||
import UnityPy | ||
from collections import Counter | ||
import json | ||
|
||
TYPES = [ | ||
# Images | ||
'Sprite', | ||
'Texture2D', | ||
# Text (filish) | ||
'TextAsset', | ||
'Shader', | ||
'MonoBehaviour', | ||
'Mesh' | ||
# Font | ||
'Font', | ||
# Audio | ||
'AudioClip', | ||
] | ||
|
||
ROOT = os.path.dirname(os.path.realpath(__file__)) | ||
DST = os.path.join(ROOT, "extracted") | ||
IGNOR_DIR_COUNT=0 | ||
|
||
def extract_assets(src): | ||
# load source | ||
env = UnityPy.load(src) | ||
|
||
# iterate over assets | ||
for asset in env.assets: | ||
# assets without container / internal path will be ignored for now | ||
if not asset.container: | ||
continue | ||
# filter objects and put Texture2Ds at the end of the list | ||
objs = sorted( | ||
( | ||
obj | ||
for obj in asset.get_objects() | ||
if obj.type.name in TYPES | ||
), | ||
key=lambda x: 1 if x.type == "Texture2D" else 0 | ||
) | ||
cobjs = sorted( | ||
( | ||
(key, obj) | ||
for key, obj in asset.container.items() | ||
if obj.type.name in TYPES | ||
), | ||
key=lambda x: 1 if x[1].type == "Texture2D" else 0 | ||
) | ||
# check which mode we will have to use | ||
num_cont = len(cobjs) | ||
num_objs = len(objs) | ||
|
||
# check if container contains all important assets, if yes, just ignore the container | ||
if num_objs <= num_cont * 2: | ||
for asset_path, obj in cobjs: | ||
fp = os.path.join(DST, *asset_path.split('/') | ||
[IGNOR_DIR_COUNT:]) | ||
export_obj(obj, fp) | ||
|
||
# otherwise use the container to generate a path for the normal objects | ||
else: | ||
extracted = [] | ||
# find the most common path | ||
occurence_count = Counter(os.path.splitext(asset_path)[ | ||
0] for asset_path in asset.container.keys()) | ||
local_path = os.path.join( | ||
DST, *occurence_count.most_common(1)[0][0].split('/')[IGNOR_DIR_COUNT:]) | ||
|
||
for obj in objs: | ||
if obj.path_id not in extracted: | ||
try: | ||
extracted.extend(export_obj( | ||
obj, local_path, append_name=True)) | ||
except Exception as e: | ||
print(e, obj.path_id) | ||
|
||
|
||
def export_obj(obj, fp: str, append_name: bool = False) -> list: | ||
if obj.type not in TYPES: | ||
return [] | ||
|
||
data = obj.read() | ||
if append_name: | ||
fp = os.path.join(fp, data.name) | ||
|
||
fp, extension = os.path.splitext(fp) | ||
os.makedirs(os.path.dirname(fp), exist_ok=True) | ||
|
||
# streamlineable types | ||
export = None | ||
if obj.type == 'TextAsset': | ||
if not extension: | ||
extension = '.txt' | ||
export = data.script | ||
|
||
elif obj.type == "Font": | ||
if data.m_FontData: | ||
extension = ".ttf" | ||
if data.m_FontData[0:4] == b"OTTO": | ||
extension = ".otf" | ||
export = data.m_FontData | ||
else: | ||
return [obj.path_id] | ||
|
||
elif obj.type == "Mesh": | ||
extension = ".obf" | ||
export = data.export().encode("utf8") | ||
|
||
elif obj.type == "Shader": | ||
extension = ".txt" | ||
export = data.export().encode("utf8") | ||
|
||
elif obj.type == "MonoBehaviour": | ||
# The data structure of MonoBehaviours is custom | ||
# and is stored as nodes | ||
# If this structure doesn't exist, | ||
# it might still help to at least save the binary data, | ||
# which can then be inspected in detail. | ||
if obj.serialized_type.nodes: | ||
extension = ".json" | ||
export = json.dumps( | ||
obj.read_typetree(), | ||
indent=4, | ||
ensure_ascii=False | ||
).encode("utf8") | ||
else: | ||
extension = ".bin" | ||
export = data.raw_data | ||
|
||
if export: | ||
with open(f"{fp}{extension}", "wb") as f: | ||
f.write(export) | ||
|
||
# non-streamlineable types | ||
if obj.type == "Sprite": | ||
data.image.save(f"{fp}.png") | ||
|
||
return [obj.path_id, data.m_RD.texture.path_id, getattr(data.m_RD.alphaTexture, 'path_id', None)] | ||
|
||
elif obj.type == "Texture2D": | ||
if not os.path.exists(fp) and data.m_Width: | ||
# textures can have size 0..... | ||
data.image.save(f"{fp}.png") | ||
|
||
elif obj.type == "AudioClip": | ||
samples = data.samples | ||
if len(samples) == 0: | ||
pass | ||
elif len(samples) == 1: | ||
with open(f"{fp}.wav", "wb") as f: | ||
f.write(list(data.samples.values())[0]) | ||
else: | ||
os.makedirs(fp, exist_ok=True) | ||
for name, clip_data in samples.items(): | ||
with open(os.path.join(fp, f"{name}.wav"), "wb") as f: | ||
f.write(clip_data) | ||
return [obj.path_id] | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Blue Archive asset downloader | ||
|
||
A small project that downloads all assets of the global version of Blue Archive and extracts them while it's at it. | ||
|
||
The script updates the assets and even its own parameters on its own, | ||
so all you have to do is execute the download_assets.py script after every update to get the latest files. | ||
|
||
## Script Requirements | ||
|
||
``` | ||
- Python 3.6+ | ||
- UnityPy 1.7.21 | ||
- requests | ||
```cmd | ||
pip install UnityPy==1.7.21 | ||
pip install requests | ||
``` | ||
|
||
|
||
## TODO | ||
|
||
- decryption of some files | ||
- |
Oops, something went wrong.