diff --git a/README.ja.md b/README.ja.md index 6da8f0d..41077f7 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1,5 +1,5 @@ # KoikatuCharaLoader -このプログラムは、コイカツやエモクリのキャラカードをPythonで読み込む・書き込むためのライブラリです。(キャラカードの他にもセーブデータ等も完全ではないですが読み込めます) +このプログラムは、コイカツ・エモクリ・ハニカムのキャラカードをPythonで読み込む・書き込むためのライブラリです。(キャラカードの他にもセーブデータ等も完全ではないですが読み込めます) [![](https://img.shields.io/pypi/v/kkloader)](https://pypi.org/project/kkloader/) [![Downloads](https://pepy.tech/badge/kkloader)](https://pepy.tech/project/kkloader) @@ -26,6 +26,19 @@ $ python ``` 簡単! +# 使用できるクラスの一覧 + +- 読み込みと書き込み両方に対応 + - KoikatuCharaData + - EmocreCharaData + - HoneycomeCharaData +- 読み込みのみ対応 + - KoikatuSaveData + - EmocreMapData + - EmocreSceneData + +いずれのクラスも `from kkloader import KoikatuCharaData` のようにインポートし、 `.load(filename)` のようにファイルを読み込むことができます。 + # ブロックデータについて コイカツのキャラデータは"ブロックデータ"というデータのかたまりから成っています。 @@ -165,3 +178,6 @@ kc.save("./data/kk_chara_modified.png") # 謝辞 - [martinwu42/pykoikatu](https://github.com/martinwu42/pykoikatu) + +# 連絡先 +[@tropical_362827](https://twitter.com/tropical_362827) \ No newline at end of file diff --git a/README.md b/README.md index 5a48b61..b0bb421 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # KoikatuCharaLoader -A simple deserializer / serializer for Koikatu / EmotionCreators character data. +A simple deserializer / serializer for Koikatu / EmotionCreators / Honeycome character data. [![](https://img.shields.io/pypi/v/kkloader)](https://pypi.org/project/kkloader/) [![Downloads](https://pepy.tech/badge/kkloader)](https://pepy.tech/project/kkloader) @@ -28,6 +28,19 @@ $ python ``` that's it :) +# List of Classes + +- Supports saving and loading + - KoikatuCharaData + - EmocreCharaData + - HoneycomeCharaData +- Supports loading only + - KoikatuSaveData + - EmocreMapData + - EmocreSceneData + +Any class can be imported like `from kkloader import KoikatuCharaData` and data can be loaded using the `.load(filename)` method. + # Mechanism of the Blockdata A character data of koikatu consists of some *blockdata*. @@ -163,3 +176,7 @@ kc.save("./data/kk_chara_modified.png") # Acknowledgements - [martinwu42/pykoikatu](https://github.com/martinwu42/pykoikatu) + +# Contact + +[@tropical_362827](https://twitter.com/tropical_362827) \ No newline at end of file diff --git a/data/hc_chara.png b/data/hc_chara.png new file mode 100644 index 0000000..aec0f61 Binary files /dev/null and b/data/hc_chara.png differ diff --git a/kkloader/EmocreCharaData.py b/kkloader/EmocreCharaData.py index 12bddb9..7628ac6 100644 --- a/kkloader/EmocreCharaData.py +++ b/kkloader/EmocreCharaData.py @@ -1,12 +1,20 @@ import struct +import kkloader import kkloader.KoikatuCharaData from kkloader.funcs import get_png, load_length, load_type class EmocreCharaData(kkloader.KoikatuCharaData): def __init__(self): - pass + self.modules = { + "Custom": kkloader.kk_Custom, + "Coordinate": kkloader.kk_Coordinate, + "Parameter": kkloader.kk_Parameter, + "Status": kkloader.kk_Status, + "About": kkloader.kk_About, + "KKEx": kkloader.kk_KKEx, + } def _load_header(self, data, **kwargs): self.image = get_png(data) diff --git a/kkloader/HoneycomeCharaData.py b/kkloader/HoneycomeCharaData.py new file mode 100644 index 0000000..f32dc14 --- /dev/null +++ b/kkloader/HoneycomeCharaData.py @@ -0,0 +1,96 @@ +import io +import struct + +import kkloader.KoikatuCharaData +from kkloader.funcs import load_length, msg_pack, msg_unpack +from kkloader.KoikatuCharaData import BlockData + + +class HoneycomeCharaData(kkloader.KoikatuCharaData): + def __init__(self): + self.modules = { + "Custom": Custom, + "Coordinate": Coordinate, + "Parameter": kkloader.kk_Parameter, + "Status": kkloader.kk_Status, + "Graphic": Graphic, + "About": kkloader.kk_About, + "GameParameter_HCP": GameParameter_HCP, + "GameInfo_HCP": GameInfo_HCP, + } + + +class Custom(BlockData): + fields = ["face", "body"] + + def __init__(self, data, version): + self.name = "Custom" + self.version = version + self.data = {} + data_stream = io.BytesIO(data) + for f in self.fields: + self.data[f] = msg_unpack(load_length(data_stream, "i")) + + def serialize(self): + data = [] + pack = struct.Struct("i") + for f in self.fields: + field_s, length = msg_pack(self.data[f]) + data.append(pack.pack(length)) + data.append(field_s) + serialized = b"".join(data) + return serialized, self.name, self.version + + +class Coordinate(BlockData): + fields = [ + "clothes", + "accessory", + "makeup", + "hair", + "nail", + ] + + def __init__(self, data, version): + self.name = "Coordinate" + self.version = version + if data is None: + return + + self.data = [] + for coordinate_bytes in msg_unpack(data): + data_stream = io.BytesIO(coordinate_bytes) + coordinate_dict = {} + for f in self.fields: + coordinate_dict[f] = msg_unpack(load_length(data_stream, "i")) + self.data.append(coordinate_dict) + + def serialize(self): + data = [] + for i in self.data: + c = [] + pack = struct.Struct("i") + + for f in self.fields: + serialized, length = msg_pack(i[f]) + c.extend([pack.pack(length), serialized]) + + data.append(b"".join(c)) + serialized_all, _ = msg_pack(data) + + return serialized_all, self.name, self.version + + +class Graphic(BlockData): + def __init__(self, data, version): + super().__init__(name="Graphic", data=data, version=version) + + +class GameParameter_HCP(BlockData): + def __init__(self, data, version): + super().__init__(name="GameParameter_HCP", data=data, version=version) + + +class GameInfo_HCP(BlockData): + def __init__(self, data, version): + super().__init__(name="GameInfo_HCP", data=data, version=version) diff --git a/kkloader/KoikatuCharaData.py b/kkloader/KoikatuCharaData.py index 7c490de..b607b6d 100644 --- a/kkloader/KoikatuCharaData.py +++ b/kkloader/KoikatuCharaData.py @@ -16,10 +16,15 @@ def bin_to_str(serial): class KoikatuCharaData: - readable_formats = ["Custom", "Coordinate", "Parameter", "Status", "About", "KKEx"] - def __init__(self): - pass + self.modules = { + "Custom": Custom, + "Coordinate": Coordinate, + "Parameter": Parameter, + "Status": Status, + "About": About, + "KKEx": KKEx, + } @classmethod def load(cls, filelike, contains_png=True): @@ -68,8 +73,8 @@ def _load_blockdata(self, data): data = lstinfo_raw[pos : pos + size] self.blockdata.append(name) - if name in self.readable_formats: - setattr(self, name, globals()[name](data, version)) + if name in self.modules.keys(): + setattr(self, name, self.modules[name](data, version)) else: setattr(self, name, UnknownBlockData(name, data, version)) self.unknown_blockdata.append(name) diff --git a/kkloader/__init__.py b/kkloader/__init__.py index b13f729..efb7b48 100644 --- a/kkloader/__init__.py +++ b/kkloader/__init__.py @@ -1,5 +1,14 @@ -from .KoikatuCharaData import * # noqa isort: skip +from .KoikatuCharaData import ( # noqa isort: skip + Custom as kk_Custom, + Coordinate as kk_Coordinate, + Parameter as kk_Parameter, + About as kk_About, + Status as kk_Status, + KKEx as kk_KKEx, + KoikatuCharaData, +) from .KoikatuSaveData import KoikatuSaveData # noqa isort: skip from .EmocreCharaData import EmocreCharaData # noqa from .EmocreMapData import EmocreMapData # noqa from .EmocreSceneData import EmocreSceneData # noqa +from .HoneycomeCharaData import HoneycomeCharaData # noqa diff --git a/pyproject.toml b/pyproject.toml index 7cc286e..5ac7773 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "kkloader" version = "0.0.0" description = "a simple deserializer / serializer for Koikatu / EmotionCreators data." -authors = ["great-majority "] +authors = ["great-majority "] homepage = "https://github.com/great-majority/KoikatuCharaLoader" readme = "README.md" diff --git a/test/test_charadata.py b/test/test_charadata.py index 51e6463..3389bf4 100644 --- a/test/test_charadata.py +++ b/test/test_charadata.py @@ -1,6 +1,6 @@ import tempfile -from kkloader import EmocreCharaData, KoikatuCharaData +from kkloader import EmocreCharaData, HoneycomeCharaData, KoikatuCharaData def test_load_character(): @@ -37,6 +37,12 @@ def test_load_mod_character(): assert hasattr(kc, "KKEx") +def test_load_honeycome_character(): + hc = HoneycomeCharaData.load("./data/hc_chara.png") + for f in hc.modules.keys(): + assert hasattr(hc, f) + + def test_save_character(): tmpfile = tempfile.NamedTemporaryFile() kc = KoikatuCharaData.load("./data/kk_chara.png") @@ -64,6 +70,16 @@ def test_save_emocre_character(): assert bytes(ec) == bytes(ec2) +def test_save_honeycome_character(): + tmpfile = tempfile.NamedTemporaryFile() + hc = HoneycomeCharaData.load("./data/hc_chara.png") + hc.save(tmpfile.name) + hc2 = HoneycomeCharaData.load(tmpfile.name) + assert hc["Parameter"]["lastname"] == hc2["Parameter"]["lastname"] + assert hc["Parameter"]["firstname"] == hc2["Parameter"]["firstname"] + assert bytes(hc) == bytes(hc2) + + def test_json_character(): kc = KoikatuCharaData.load("./data/kk_chara.png") tmpfile = tempfile.NamedTemporaryFile() @@ -80,3 +96,10 @@ def test_json_emocre_character(): ec = EmocreCharaData.load("./data/ec_chara.png") tmpfile = tempfile.NamedTemporaryFile() ec.save_json(tmpfile.name) + + +def test_json_honeycome(): + hc = HoneycomeCharaData.load("./data/hc_chara.png") + tmpfile = tempfile.NamedTemporaryFile() + hc.save_json("test.json") + hc.save_json(tmpfile.name)