diff --git a/README.md b/README.md index 9902db24..8c196953 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Construct type definitions for Mercury Engine | BCUT | ✗ | ✗ | Missing | Missing | | BCWAV | ✗ | ✗ | Missing | Missing | | BFGRP | Missing | Missing | ✗ | ✗ | -| BFONT | ✗ | ✗ | ✗ | ✗ | +| BFONT | ✓ | ✓ | ✓ | ✓ | | BFSAR | Missing | Missing | ✗ | ✗ | | BFSTM | Missing | Missing | ✗ | ✗ | | BGSNDS | Missing | Missing | ✓ | ✓ | diff --git a/src/mercury_engine_data_structures/formats/__init__.py b/src/mercury_engine_data_structures/formats/__init__.py index bb0e37e3..cbc8532f 100644 --- a/src/mercury_engine_data_structures/formats/__init__.py +++ b/src/mercury_engine_data_structures/formats/__init__.py @@ -5,6 +5,7 @@ from mercury_engine_data_structures.formats.bcmdl import Bcmdl from mercury_engine_data_structures.formats.bcskla import Bcskla from mercury_engine_data_structures.formats.bctex import Bctex +from mercury_engine_data_structures.formats.bfont import Bfont from mercury_engine_data_structures.formats.bgsnds import Bgsnds from mercury_engine_data_structures.formats.bldef import Bldef from mercury_engine_data_structures.formats.blsnd import Blsnd @@ -55,6 +56,7 @@ "BCMDL": Bcmdl, "BCSKLA": Bcskla, "BCTEX": Bctex, + "BFONT": Bfont, "BGSNDS": Bgsnds, "BLDEF": Bldef, "BLSND": Blsnd, diff --git a/src/mercury_engine_data_structures/formats/bfont.py b/src/mercury_engine_data_structures/formats/bfont.py new file mode 100644 index 00000000..ad6c39ca --- /dev/null +++ b/src/mercury_engine_data_structures/formats/bfont.py @@ -0,0 +1,72 @@ +import math + +import construct +from construct import Construct +from construct.core import ( + Const, + Int16sl, + Int16ul, + Int32ul, + Int64ul, + Rebuild, + Struct, + Terminated, +) + +from mercury_engine_data_structures.common_types import StrId, VersionAdapter +from mercury_engine_data_structures.construct_extensions.alignment import AlignTo +from mercury_engine_data_structures.formats.base_resource import BaseResource +from mercury_engine_data_structures.game_check import Game, GameSpecificStruct + + +# helper func to calculate padding for rebuild +def calc_padding(pad_to, offset): + return math.ceil(offset / pad_to) * pad_to + + +Sprite = Struct("pos" / Int16sl[2], "width" / Int16sl, "height" / Int16sl, "unk1" / Int16sl, "unk2" / Int16sl[2]) + +BFONT_MSR = Struct( + "magic" / Const(b"MFNT"), + "version" / VersionAdapter("1.9.0"), + Const(0x28, Int32ul), # pointer to name + "width" / Int32ul, + "height" / Int32ul, + "unk1" / Int32ul, + "unk2" / Int32ul, + "glyph_count" / Rebuild(Int32ul, lambda ctx: len(ctx.glyph_data)), + "_data_start" / Rebuild(Int32ul, lambda ctx: calc_padding(0x10, 0x28 + len(ctx.atlas_path))), + "_buct_name_offset" / Rebuild(Int32ul, lambda ctx: calc_padding(0x4, ctx._data_start + ctx.glyph_count * 14)), + "atlas_path" / StrId, + AlignTo(0x10), + "glyph_data" / Sprite[construct.this.glyph_count], + AlignTo(0x4), + "buct_path" / StrId, + Terminated, +) + +BFONT_DREAD = Struct( + "magic" / Const(b"MFNT"), + "version" / VersionAdapter("1.10.0"), + Const(0x38, Int64ul), # pointer to name + "width" / Int32ul, + "height" / Int32ul, + "unk1" / Int16ul, + Const(b"\xff\xff"), + "unk2" / Int32ul, + "glyph_count" / Rebuild(Int32ul, lambda ctx: len(ctx.glyph_data)), + Const(b"\xff\xff\xff\xff"), + "_data_start" / Rebuild(Int64ul, lambda ctx: calc_padding(0x10, 0x38 + len(ctx.atlas_path))), + "_buct_name_offset" / Rebuild(Int64ul, lambda ctx: ctx._data_start + ctx.glyph_count * 14), + "atlas_path" / StrId, + AlignTo(0x10, pattern=b"\xff"), + "glyph_data" / Sprite[construct.this.glyph_count], + "buct_path" / StrId, + Terminated, +) + + +class Bfont(BaseResource): + @classmethod + def construct_class(cls, target_game: Game) -> Construct: + return GameSpecificStruct({Game.SAMUS_RETURNS: BFONT_MSR, Game.DREAD: BFONT_DREAD}[target_game], target_game) diff --git a/tests/formats/test_bfont.py b/tests/formats/test_bfont.py new file mode 100644 index 00000000..260176c2 --- /dev/null +++ b/tests/formats/test_bfont.py @@ -0,0 +1,15 @@ +import pytest +from tests.test_lib import parse_build_compare_editor + +from mercury_engine_data_structures import dread_data, samus_returns_data +from mercury_engine_data_structures.formats.bfont import Bfont + + +@pytest.mark.parametrize("bfont_path", dread_data.all_files_ending_with(".bfont")) +def test_buct_dread(dread_file_tree, bfont_path): + parse_build_compare_editor(Bfont, dread_file_tree, bfont_path) + + +@pytest.mark.parametrize("bfont_path", samus_returns_data.all_files_ending_with(".bfont")) +def test_buct_sr(samus_returns_tree, bfont_path): + parse_build_compare_editor(Bfont, samus_returns_tree, bfont_path)