https://snowyegret.tistory.com/69
UnityPy를 이용한 bundle파일 내 Monobehaviour 일괄수정
텍스트가 모두 .bundle 파일 안에 들어있고, 대사 파일이 여기저기 파편화되어 있기에 방법을 찾아보다 UnityPy라는 모듈을 사용하게 되었다. 이 게시글에선 특정 방법만을 다룰 것이나, 쉽게 응용
snowyegret.tistory.com
텍스트가 모두 .bundle 파일 안에 들어있고, 대사 파일이 여기저기 파편화되어 있기에
방법을 찾아보다 UnityPy라는 모듈을 사용하게 되었다.
이 게시글에선 특정 방법만을 다룰 것이나, 쉽게 응용이 가능하다.
1. UnityPy
UnityPy github을 보면 사용법이 이런 식으로 되어있다.
( https://github.com/K0lb3/UnityPy )
번들 파일을 UnityPy.load()를 이용해 로드한 후, 오브젝트를 쭉 순회하며 .type.name이 MonoBehaviour인 것들에 대한 작업을 수행한다.
2. CSV 파일로 텍스트 추출
import UnityPy
import json
import os
import csv
def sanitize_text(text):
return text.replace("\r\n", "\\r\\n").replace("\n", "\\n").replace("\r", "\\r")
# fmt: off
def work_export(filename):
with open("text.csv", "a", encoding="utf-8", newline="") as f:
writer = csv.writer(f, quoting=csv.QUOTE_ALL)
clean_filename = os.path.splitext(os.path.basename(filename))[0]
env = UnityPy.load(filename)
for obj in env.objects:
if obj.type.name == "MonoBehaviour" and obj.serialized_type.nodes:
tree = obj.read_typetree()
if tree.get('storyText') == None:
continue
if tree.get('itemId') == None:
continue
if tree.get('character') == None:
continue
if tree.get('character').get('m_PathID') == None:
continue
text = sanitize_text(tree.get('storyText'))
item_id = tree.get('itemId')
character_id = tree.get('character').get('m_PathID')
writer.writerow([clean_filename, item_id, character_id, text, ""])
def export_bundle():
with open("text.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f, quoting=csv.QUOTE_ALL)
writer.writerow(["filename", "item_id", "character_id", "src", "dst"])
filename_lst = [i for i in os.listdir("./StandaloneWindows64") if i.endswith(".bundle")]
for filename in filename_lst:
work_export(f"./StandaloneWindows64/{filename}")
if __name__ == "__main__":
export_bundle()
개인 취향 문제로, \r이나 \n같은 이스케이프 문자를 모두 변환하여 한 줄로 만들었다.
또한, quoting 옵션을 csv.QUOTE_ALL로 주었다.
정상적으로 모든 텍스트가 추출된 것을 볼 수 있다.
이제 5번째 열 (dst 컬럼)에다가 번역하면 된다.
3. 번역한 텍스트를 번들에 삽입
일단, CSV를 이용해 딕셔너리를 만든다. 내가 번역문을 불러올 때 주로 사용하는 방법이다.
위 경우 i[0]이 번들 파일명, i[1]이 item_id, i[2]가 character_id, i[3]이 원문, i[4]가 번역문이다.
dict 구조로 보자면 아래 사진과 같이 될 것이다.
고유한 값인 번들 파일명과 고유한 값인 item_id를 기반으로 CSV에 있는 값들을 집어넣었다.
작업할 번들 파일 목록 작성도 좀 특이하게 진행해야 한다.
CSV에 번들 파일명이 기록되어 있으니, 작업하지 않아도 되는(CSV에 내용이 작성되어있지 않은) 파일은 패스할 수 있다.
이를 통해 작업시간을 단축할 수 있다.
import UnityPy
import json
import os
import csv
from pprint import pprint
def restore_text(text):
return text.replace("\\r\\n", "\r\n").replace("\\n", "\n").replace("\\r", "\r")
# fmt: off
def work_import(filename, translate_dict):
edit_switch = False
clean_filename = os.path.splitext(os.path.basename(filename))[0]
env = UnityPy.load(filename)
for obj in env.objects:
if obj.type.name == "MonoBehaviour" and obj.serialized_type.nodes:
tree = obj.read_typetree()
if tree.get('storyText') == None:
continue
if tree.get('itemId') == None:
continue
if tree.get('character') == None:
continue
if tree.get('character').get('m_PathID') == None:
continue
item_id = str(tree.get('itemId'))
# 번역된 것이 없다면 건너뛰기
if translate_dict[clean_filename][item_id]["dst"] == "":
continue
translated = restore_text(translate_dict[clean_filename][item_id]["dst"])
tree["storyText"] = translated
edit_switch = True
obj.save_typetree(tree)
if edit_switch:
with open(f"./StandaloneWindows64_new/{clean_filename}.bundle", "wb") as f:
f.write(env.file.save())
def import_bundle():
with open("./text.csv", "r", encoding="utf-8", newline="") as f:
reader = list(csv.reader(f))
del reader[0]
translate_dict = {}
for i in reader:
if i[0] not in translate_dict:
translate_dict[i[0]] = {}
translate_dict[i[0]][i[1]] = {
"item_id": i[1],
"character_id": i[2],
"src": i[3],
"dst": i[4],
}
filename_lst = [
filename
for filename in os.listdir("./StandaloneWindows64")
if filename.endswith(".bundle") and os.path.splitext(filename)[0] in translate_dict
]
for filename in filename_lst:
work_import(f"./StandaloneWindows64/{filename}", translate_dict)
if __name__ == "__main__":
import_bundle()
이것이 내가 최종적으로 사용한 전체 코드이다.
sanitize_text() 함수를 수정하여 수정했던 이스케이프 문자를 복구시키고,
work_import() 함수를 수정하여 번역문을 불러오도록 하였다.
수정 이후 obj.save_typetree(tree)를 하여 수정한 dict 데이터가 저장되도록 하였고
f.write(env.file.save())를 하여 수정된 번들 데이터가 저장되도록 하였다.
edit_switch라는 bool 변수를 통해, 번들 파일이 실질적으로 수정된 경우에만 저장하도록 하였다.
'한글화' 카테고리의 다른 글
자동번역기 TMP 폰트 모음(김좌진장군체, 전주완판본-순체, 강원교육-새음체) (12) | 2024.10.09 |
---|---|
안성탕면체 TMP 폰트 (2) | 2024.10.06 |
유니티 게임 한글화 - assetbundle crc체크 우회 (0) | 2024.09.12 |
BepInEx 6 - 667+ Mono 에서 동작하는 자동번역기 (0) | 2024.08.06 |
Fantasy General 2 한글패치 (6) | 2024.07.14 |