From ca8a88cd7ebaf541db4c196de8714227ed6e866f Mon Sep 17 00:00:00 2001 From: mkp Date: Thu, 27 Nov 2025 12:09:54 +0100 Subject: [PATCH] maxdiff: properly deal with old patches that have garbage trailing their json data --- maxdiff/freezing_utils.py | 35 ++++++++++++++++-- maxdiff/tests/test.py | 10 +++++ .../tests/test_baselines/WithGarbage.amxd.txt | 12 ++++++ maxdiff/tests/test_files/WithGarbage.amxd | Bin 0 -> 88505 bytes maxdiff/tests/test_rewrite_baselines.py | 1 + 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 maxdiff/tests/test_baselines/WithGarbage.amxd.txt create mode 100644 maxdiff/tests/test_files/WithGarbage.amxd diff --git a/maxdiff/freezing_utils.py b/maxdiff/freezing_utils.py index cafe935..872f033 100644 --- a/maxdiff/freezing_utils.py +++ b/maxdiff/freezing_utils.py @@ -86,10 +86,11 @@ def get_patcher_dict(entry: device_entry_with_data): device_data_text = "" try: - if patch_data[len(patch_data) - 1] == 0: - device_data_text = patch_data[: len(patch_data) - 1].decode("utf-8") - else: - device_data_text = patch_data.decode("utf-8") + device_data_text = patch_data.decode("utf-8") + + # Past Max versions could write patcher data with a trailing 0 + # or with trailing garbage. + device_data_text = strip_trailing_nonjson(device_data_text) except Exception as e: print(f"Error getting patch data as text for entry {name}: {e}") return {} @@ -106,3 +107,29 @@ def get_patcher_dict(entry: device_entry_with_data): except: print(f"Content of entry {name} does not seem to be a patcher") return {} + + +def strip_trailing_nonjson(device_data_text: str) -> str: + """ + This function returns (possibly trimmed) JSON text to pass to json.loads(). + """ + decoder = json.JSONDecoder() + try: + _, end = decoder.raw_decode(device_data_text) + return device_data_text[:end].rstrip() + except json.JSONDecodeError: + pass + + # find all candidate closing positions for objects/arrays + closers = [i for i, ch in enumerate(device_data_text) if ch in ("}", "]")] + # try from last to first — prefer the longest valid prefix + for pos in reversed(closers): + candidate = device_data_text[: pos + 1] + try: + json.loads(candidate) + return candidate + except Exception: + continue + + # nothing worked; return original so caller can log/handle the parse error + return device_data_text diff --git a/maxdiff/tests/test.py b/maxdiff/tests/test.py index 3eac5cf..7ffeecd 100644 --- a/maxdiff/tests/test.py +++ b/maxdiff/tests/test.py @@ -102,6 +102,16 @@ def test_parse_maxpat_with_merge_conficts(self): actual = parse(test_path) self.assertEqual(expected, actual) + def test_parse_with_garbage(self): + self.maxDiff = None + + expected_path, test_path = get_test_path_files("WithGarbage.amxd") + + with open(expected_path, mode="r") as expected_file: + expected = expected_file.read() + actual = parse(test_path) + self.assertEqual(expected, actual) + def get_test_path_files(file_name): expected = get_test_path_file(f"test_baselines/{file_name}.txt") diff --git a/maxdiff/tests/test_baselines/WithGarbage.amxd.txt b/maxdiff/tests/test_baselines/WithGarbage.amxd.txt new file mode 100644 index 0000000..88812f3 --- /dev/null +++ b/maxdiff/tests/test_baselines/WithGarbage.amxd.txt @@ -0,0 +1,12 @@ +Audio Effect Device +------------------- +Device is frozen +----- Contents ----- +Livesync Leader.amxd: 88337 bytes, modified at 2020/06/14 09:47:41 UTC <= Device + +Total - Counting every abstraction instance - Indicates loading time + Object instances: 130 + Connections: 151 +Unique - Counting abstractions once - Indicates maintainability + Object instances: 130 + Connections: 151 diff --git a/maxdiff/tests/test_files/WithGarbage.amxd b/maxdiff/tests/test_files/WithGarbage.amxd new file mode 100644 index 0000000000000000000000000000000000000000..2040ccea5ba914af83255dc569349d151442f8b3 GIT binary patch literal 88505 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 literal 0 HcmV?d00001 diff --git a/maxdiff/tests/test_rewrite_baselines.py b/maxdiff/tests/test_rewrite_baselines.py index 390cac9..14b1db6 100644 --- a/maxdiff/tests/test_rewrite_baselines.py +++ b/maxdiff/tests/test_rewrite_baselines.py @@ -14,6 +14,7 @@ def run(): rewrite_file("Test.amxd") rewrite_file("EncryptedTest.amxd") rewrite_file("FrozenTest.amxd") + rewrite_file("WithGarbage.amxd") rewrite_file("Test.maxpat") rewrite_file("Test Project/Zipped.als") rewrite_file("Test Project/Test.als")