From db60cc548fbc218a25421e36876c09f68f37d2bb Mon Sep 17 00:00:00 2001 From: grub-basket Date: Wed, 29 Jul 2026 23:18:02 -0700 Subject: [PATCH] Fix broken --create (JSON repack) round-trip Repacking a JSON dump back into a resource fork was broken in several ways: 1. Critical: do_pack() passed encoding=args.encoding to json_to_resource_fork(), which takes no such argument, so every 'rsrcdump -c' invocation raised TypeError before doing any work. The encoding is already applied globally via set_global_encoding(), so drop the extra kwarg. 2. Resource-type JSON keys were written with res_type.decode(), but repack parses them with parse_type_name() (URL-unquote). These aren't inverses: a FourCC with a high byte (e.g. b'\xA9icn') or trailing spaces fails to round-trip. Emit the key with sanitize_type_name(), matching the resource sub-directory name and parse_type_name(). 3. Repack detected the metadata block with 'len(type_name) > 4', which also dropped legitimate URL-encoded type keys longer than 4 chars (e.g. '%A9icn'). Match the '_metadata' key explicitly instead. Without this, fix (2)'s encoded keys would be silently skipped on repack. 4. JSONEncoderBase16Fallback.default() returned a new encoder instance for non-bytes objects instead of delegating to super().default(), which would recurse into RecursionError instead of raising a clear TypeError. Delegate to super(). Verified an extract-key -> repack round-trip for both normal and high-byte FourCCs. --- rsrcdump/__main__.py | 3 +-- rsrcdump/jsonio.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/rsrcdump/__main__.py b/rsrcdump/__main__.py index 3ab6a1c..69fe52c 100644 --- a/rsrcdump/__main__.py +++ b/rsrcdump/__main__.py @@ -179,8 +179,7 @@ def do_pack(): json_blob, converters=converters, only_types=only_types, - skip_types=skip_types, - encoding=args.encoding) + skip_types=skip_types) binary_fork = fork.pack() diff --git a/rsrcdump/jsonio.py b/rsrcdump/jsonio.py index 3e889c4..73516e6 100644 --- a/rsrcdump/jsonio.py +++ b/rsrcdump/jsonio.py @@ -14,7 +14,7 @@ def default(self, o: Any): if isinstance(o, bytes): return base64.b16encode(o).decode('ascii') else: - return JSONEncoderBase16Fallback(self, o) + return super().default(o) def resource_fork_to_json( @@ -39,7 +39,11 @@ def resource_fork_to_json( errors = [] for res_type, res_dir in fork.tree.items(): - res_type_key = res_type.decode(get_global_encoding(), 'backslashreplace') + # Use the same encoding as the resource sub-directory name so the key + # round-trips through parse_type_name() on repack. Decoding the raw + # bytes here instead would not survive parse_type_name()'s unquote step + # for FourCCs containing high bytes or trailing spaces. + res_type_key = sanitize_type_name(res_type) if res_type in exclude_types: continue @@ -132,7 +136,10 @@ def json_to_resource_fork( fork.junk_filerefnum = json_blob['_metadata']['junk2'] for type_name, type_records in json_blob.items(): - if len(type_name) > 4: # probably metadata + # Skip the metadata block. (A plain length check would wrongly drop + # URL-encoded type keys such as "%A9icn", which are longer than 4 + # characters yet decode back to a 4-byte FourCC.) + if type_name == "_metadata": continue res_type = parse_type_name(type_name)