diff --git a/blender_asset_tracer/blendfile/__init__.py b/blender_asset_tracer/blendfile/__init__.py index 5335678..808c1b5 100644 --- a/blender_asset_tracer/blendfile/__init__.py +++ b/blender_asset_tracer/blendfile/__init__.py @@ -456,22 +456,26 @@ class BlendFileBlock: yield from self.get_recursive_iter(f.name.name_only, path_full, default=default, null_terminated=null_terminated, as_str=as_str) - def get_data_hash(self): # TODO(Sybren): port to BAT + def hash(self) -> int: + """Generate a pointer-independent hash for the block. + + Generates a 'hash' that can be used instead of addr_old as block id, + which should be 'stable' across .blend file load & save (i.e. it does + not changes due to pointer addresses variations). """ - Generates a 'hash' that can be used instead of addr_old as block id, and that should be 'stable' across .blend - file load & save (i.e. it does not changes due to pointer addresses variations). - """ - # TODO This implementation is most likely far from optimal... and CRC32 is not renown as the best hashing - # algo either. But for now does the job! + # TODO This implementation is most likely far from optimal... and CRC32 + # is not kown as the best hashing algo either. But for now does the job! import zlib - def _is_pointer(self, k): - return self.file.structs[self.sdna_index].field_from_path( - self.file.header, self.file.handle, k).dna_name.is_pointer + + dna_type = self.dna_type + pointer_size = self.bfile.header.pointer_size hsh = 1 - for k, v in self.items_recursive(): - if not _is_pointer(self, k): - hsh = zlib.adler32(str(v).encode(), hsh) + for path, value in self.items_recursive(): + field, _ = dna_type.field_from_path(pointer_size, path) + if field.name.is_pointer: + continue + hsh = zlib.adler32(str(value).encode(), hsh) return hsh def set(self, path: dna.FieldPath, value): # TODO(Sybren): port to BAT diff --git a/tests/test_blendfile_modification.py b/tests/test_blendfile_modification.py index 41da5fc..5f54bd9 100644 --- a/tests/test_blendfile_modification.py +++ b/tests/test_blendfile_modification.py @@ -1,5 +1,7 @@ from shutil import copyfile +import os + from blender_asset_tracer import blendfile from abstract_test import AbstractBlendFileTest @@ -25,14 +27,35 @@ class ModifyUncompressedTest(AbstractBlendFileTest): library[b'filepath'] = b'//basic_file.blend' library[b'name'] = b'//basic_file.blend' - # Reload the blend file to inspect that it was written properly. - self.bf.close() - self.bf = blendfile.BlendFile(self.to_modify, mode='r+b') + self.reload() library = self.bf.code_index[b'LI'][0] self.assertEqual(b'//basic_file.blend', library[b'filepath']) self.assertEqual(b'//basic_file.blend', library[b'name']) + def test_block_hash(self): + scene = self.bf.code_index[b'SC'][0] + assert isinstance(scene, blendfile.BlendFileBlock) + + pre_hash = scene.hash() + self.assertIsInstance(pre_hash, int) + + # Change the 'ed' pointer to some arbitrary value by hacking the blend file. + psize = self.bf.header.pointer_size + field, field_offset = scene.dna_type.field_from_path(psize, b'ed') + self.bf.fileobj.seek(scene.file_offset + field_offset, os.SEEK_SET) + self.bf.fileobj.write(b'12345678'[:psize]) + + self.reload() + + scene = self.bf.code_index[b'SC'][0] + post_hash = scene.hash() + self.assertEqual(pre_hash, post_hash) + + def reload(self): + self.bf.close() + self.bf = blendfile.BlendFile(self.to_modify, mode='r+b') + class ModifyCompressedTest(AbstractBlendFileTest): def setUp(self):