Skip to content

Commit

Permalink
Handle image files erroneously marked as compressed
Browse files Browse the repository at this point in the history
For unknown reasons, some files in a package say they're compressed
(with strange decompressed sizes) but the data is actually uncompressed.
We've seen this happen before in objects.package (c0f5db0)

The Sims Life Stories' ui.package is another example. The *exact* same
file appears in the base game version, but the former says 'compressed'
and the latter says 'uncompressed' (correct, there is no QFS header).

Introduce a "data_safe" property, so the patcher continues processing.
We still want to be vigilant with "data" property since we want to catch
errors in other places (e.g. with view_package_stats.py)

Fixes #50
  • Loading branch information
lah7 committed Feb 1, 2025
1 parent 4904798 commit a94ab41
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 2 deletions.
13 changes: 13 additions & 0 deletions sims2patcher/dbpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,19 @@ def data(self) -> bytes:

return self._bytes

@property
def data_safe(self) -> bytes:
"""
Like data(), but "safe" from exceptions if the data is incorrectly marked as compressed.
The compression state of this entry will be corrected, if so.
"""
try:
return self.data
except errors.InvalidMagicHeader:
self.compress = False
self._bytes_compressed = False
return self.raw

def _compress_data(self, data: bytes) -> bytes:
"""
Return the bytes for how it will be stored in the resulting package.
Expand Down
4 changes: 2 additions & 2 deletions sims2patcher/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ def _upscale_graphic(entry: dbpf.Entry) -> bytes:
UPSCALE_FILTER may influence the resulting quality of the image.
"""
file_type = get_image_file_type(entry.data)
file_type = get_image_file_type(entry.data_safe)

if file_type == ImageFormat.UNKNOWN:
raise errors.UnknownImageFormatError()

original = Image.open(io.BytesIO(entry.data), formats=[file_type.value])
original = Image.open(io.BytesIO(entry.data_safe), formats=[file_type.value])
resized = original.resize((int(original.width * UI_MULTIPLIER), int(original.height * UI_MULTIPLIER)), resample=UPSCALE_FILTER)

output = io.BytesIO()
Expand Down
13 changes: 13 additions & 0 deletions tests/test_dbpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def setUpClass(cls):
# Known uncompressed and incompressible file (Bitmap)
cls.bmp_index = 85
cls.bmp_md5 = "4d450dd3b45e2cebae3ef949bde06292"
cls.bmp_group_id = 0x499db772
cls.bmp_instance_id = 0xecdb3005

def tearDown(self) -> None:
# Clean up temporary files
Expand Down Expand Up @@ -427,3 +429,14 @@ def test_large_file_compression(self):
pkg = dbpf.DBPF()
entry = pkg.add_entry(0x00, 0x10, 0x20, 0x30, bytearray(16 * 1024 * 1024), compress=True)
self.assertFalse(entry.compress)

def test_compression_bad_state(self):
"""Check a file incorrectly marked as compressed can still be extracted"""
package = dbpf.DBPF("tests/files/ui.package")
entry = package.entries[self.bmp_index]
entry._bytes_compressed = True # pylint: disable=protected-access
entry.compress = True

with self.assertRaises(dbpf.errors.InvalidMagicHeader):
entry.data # pylint: disable=pointless-statement
self.assertIsInstance(entry.data_safe, bytes)

0 comments on commit a94ab41

Please sign in to comment.