diff --git a/docs/export-format.md b/docs/export-format.md index eb0ec8e..380d76a 100644 --- a/docs/export-format.md +++ b/docs/export-format.md @@ -11,7 +11,7 @@ The sanitized JSON export uses: "capture_source": "npcap", "exporter": { "name": "nte-history-exporter", - "version": "0.1.4" + "version": "0.1.5" }, "banner": { "id": "Lottery_Permanent", diff --git a/pyproject.toml b/pyproject.toml index eaefb48..68af498 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "nte-history-exporter" -version = "0.1.4" +version = "0.1.5" description = "Cross-platform Neverness to Everness pull-history exporter." requires-python = ">=3.10" dependencies = [] diff --git a/src/nte_history_exporter/__init__.py b/src/nte_history_exporter/__init__.py index bbab024..1276d02 100644 --- a/src/nte_history_exporter/__init__.py +++ b/src/nte_history_exporter/__init__.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" diff --git a/src/nte_history_exporter/constants.py b/src/nte_history_exporter/constants.py index c363667..1eac2f8 100644 --- a/src/nte_history_exporter/constants.py +++ b/src/nte_history_exporter/constants.py @@ -11,7 +11,7 @@ LIMITED_CHARACTER_BANNER_NAME = "Limited Character Board" ARC_BANNER_ID = "Arc_MiracleBox" ARC_BANNER_NAME = "Arc Miracle Box" EXPORTER_NAME = "nte-history-exporter" -EXPORTER_VERSION = "0.1.4" +EXPORTER_VERSION = "0.1.5" HISTORY_REQUEST_BANNER = 4220 HISTORY_REQUEST_LENGTH = 45 diff --git a/src/nte_history_exporter/decoder/protocol.py b/src/nte_history_exporter/decoder/protocol.py index 5aeffcf..0b799ca 100644 --- a/src/nte_history_exporter/decoder/protocol.py +++ b/src/nte_history_exporter/decoder/protocol.py @@ -18,6 +18,9 @@ from nte_history_exporter.constants import ( from nte_history_exporter.mappings import REWARDS_BY_ID REWARD_ID_CHARS = frozenset("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_") +WARP_PIECE_CHASE_PATTERN = bytes.fromhex( + "c4b0ccc00000000000040000003c00000010a58d957dd1a58dad95d17dc1c400" +) def decode_reward_key(raw: bytes) -> str: @@ -141,9 +144,12 @@ def classify_result_type( chunk_without_marker: bytes, dice: int | None, dice_offset: int | None, + reward_id: str = "", ) -> tuple[str, int | None]: if dice is None or dice_offset is None: return "unknown", None + if reward_id == "Dice_ticket_01" and WARP_PIECE_CHASE_PATTERN in chunk_without_marker: + return "chase_reward", -4 if dice == 0: return "points_gift", 0 @@ -226,15 +232,15 @@ def _decode_aligned_response_records(response_content: bytes) -> list[dict[str, _, _, _, _, trim, chunk, dice, dice_raw, dice_offset = min(embedded_candidates) record_start = prev + trim full_record = response_content[prev + trim : marker_offset + len(marker) + 8] - result_type, result_source_raw = classify_result_type(chunk, dice, dice_offset) + key_hex = extract_key(chunk) + reward_id = decode_reward_key(bytes.fromhex(key_hex)) if key_hex else "" + result_type, result_source_raw = classify_result_type(chunk, dice, dice_offset, reward_id) if result_type == "points_gift": dice = 0 dice_raw = 0 elif result_type == "chase_reward": dice = -4 dice_raw = -4 - key_hex = extract_key(chunk) - reward_id = decode_reward_key(bytes.fromhex(key_hex)) if key_hex else "" reward = REWARDS_BY_ID.get(reward_id, {}) timestamp_raw = response_content[marker_offset + len(marker) : marker_offset + len(marker) + 8] timestamp_ticks, timestamp_unix, timestamp_decoded = decode_history_timestamp(timestamp_raw) diff --git a/tests/test_boundary_export.py b/tests/test_boundary_export.py index c39a14b..1a65cef 100644 --- a/tests/test_boundary_export.py +++ b/tests/test_boundary_export.py @@ -538,6 +538,20 @@ class BoundaryExportTests(unittest.TestCase): self.assertEqual(decoded["quantity"], 30) self.assertEqual(make_uid(decoded, int(reference["timestamp_group_ordinal"])), "7d035ec098f856f81b403ea538810145") + def test_warp_piece_chase_subrecord_without_prefix_marker_is_chase_reward(self): + decoded = decode_single_record( + "c1c4b0ccc00000000000040000003c00000010a58d957dd1a58dad95d17dc1c400" + "4c0000000c85c99141bdbdb17d0da185c9858dd195c90140eb2c2dd7227b23" + ) + + self.assertEqual(decoded["result_type"], "chase_reward") + self.assertEqual(decoded["result_source_raw"], -4) + self.assertEqual(decoded["dice"], -4) + self.assertEqual(decoded["dice_raw_u32"], -4) + self.assertEqual(decoded["reward_id"], "Dice_ticket_01") + self.assertEqual(decoded["reward_name"], "Warp Piece") + self.assertEqual(decoded["quantity"], 30) + def test_batched_monopoly_response_normalizes_embedded_page_header(self): page_7 = [load_v7_row("limited_all_04_v7.csv", row) for row in range(31, 36)] page_8 = [load_v7_row("limited_all_04_v7.csv", row) for row in range(36, 41)]