From b1ffbc49c97334bfaff06fb4951a2277c30c362e Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 28 Aug 2022 18:30:19 +0200 Subject: [PATCH] LttPAdjuster: fix GUI for invalid sprite files (#885) * LttPAdjuster: ignore invalid sprite files * LttPAdjuster: ignore .gitignore in sprites * LttPAdjuster: log and show message for invalid sprites * Alttp: set sprite.valid to False for bad zspr and apsprite ... ... when throwing exceptions Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- LttPAdjuster.py | 16 +++++++++- worlds/alttp/Rom.py | 74 +++++++++++++++++++++++++-------------------- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/LttPAdjuster.py b/LttPAdjuster.py index 3de6e3b1..f516a20e 100644 --- a/LttPAdjuster.py +++ b/LttPAdjuster.py @@ -752,6 +752,7 @@ class SpriteSelector(): self.window['pady'] = 5 self.spritesPerRow = 32 self.all_sprites = [] + self.invalid_sprites = [] self.sprite_pool = spritePool def open_custom_sprite_dir(_evt): @@ -833,6 +834,13 @@ class SpriteSelector(): self.window.focus() tkinter_center_window(self.window) + if self.invalid_sprites: + invalid = sorted(self.invalid_sprites) + logging.warning(f"The following sprites are invalid: {', '.join(invalid)}") + msg = f"{invalid[0]} " + msg += f"and {len(invalid)-1} more are invalid" if len(invalid) > 1 else "is invalid" + messagebox.showerror("Invalid sprites detected", msg, parent=self.window) + def remove_from_sprite_pool(self, button, spritename): self.callback(("remove", spritename)) self.spritePoolButtons.buttons.remove(button) @@ -897,7 +905,13 @@ class SpriteSelector(): sprites = [] for file in os.listdir(path): - sprites.append((file, Sprite(os.path.join(path, file)))) + if file == '.gitignore': + continue + sprite = Sprite(os.path.join(path, file)) + if sprite.valid: + sprites.append((file, sprite)) + else: + self.invalid_sprites.append(file) sprites.sort(key=lambda s: str.lower(s[1].name or "").strip()) diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index dd5cc8c4..9ca3a355 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -34,7 +34,7 @@ from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts DeathMountain_texts, \ LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \ SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names -from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen +from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen, parse_yaml from worlds.alttp.Items import ItemFactory, item_table, item_name_groups, progression_items from worlds.alttp.EntranceShuffle import door_addresses from worlds.alttp.Options import smallkey_shuffle @@ -551,18 +551,22 @@ class Sprite(): Sprite.base_data = Sprite.sprite + Sprite.palette + Sprite.glove_palette def from_ap_sprite(self, filedata): - filedata = filedata.decode("utf-8-sig") - import yaml - obj = yaml.safe_load(filedata) - if obj["min_format_version"] > 1: - raise Exception("Sprite file requires an updated reader.") - self.author_name = obj["author"] - self.name = obj["name"] - if obj["data"]: # skip patching for vanilla content - data = bsdiff4.patch(Sprite.base_data, obj["data"]) - self.sprite = data[:self.sprite_size] - self.palette = data[self.sprite_size:self.palette_size] - self.glove_palette = data[self.sprite_size + self.palette_size:] + # noinspection PyBroadException + try: + obj = parse_yaml(filedata.decode("utf-8-sig")) + if obj["min_format_version"] > 1: + raise Exception("Sprite file requires an updated reader.") + self.author_name = obj["author"] + self.name = obj["name"] + if obj["data"]: # skip patching for vanilla content + data = bsdiff4.patch(Sprite.base_data, obj["data"]) + self.sprite = data[:self.sprite_size] + self.palette = data[self.sprite_size:self.palette_size] + self.glove_palette = data[self.sprite_size + self.palette_size:] + except Exception: + logger = logging.getLogger("apsprite") + logger.exception("Error parsing apsprite file") + self.valid = False @property def author_game_display(self) -> str: @@ -659,7 +663,7 @@ class Sprite(): @staticmethod def parse_zspr(filedata, expected_kind): - logger = logging.getLogger('ZSPR') + logger = logging.getLogger("ZSPR") headerstr = "<4xBHHIHIHH6x" headersize = struct.calcsize(headerstr) if len(filedata) < headersize: @@ -667,7 +671,7 @@ class Sprite(): version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind = struct.unpack_from( headerstr, filedata) if version not in [1]: - logger.error('Error parsing ZSPR file: Version %g not supported', version) + logger.error("Error parsing ZSPR file: Version %g not supported", version) return None if kind != expected_kind: return None @@ -676,36 +680,42 @@ class Sprite(): stream.seek(headersize) def read_utf16le(stream): - "Decodes a null-terminated UTF-16_LE string of unknown size from a stream" + """Decodes a null-terminated UTF-16_LE string of unknown size from a stream""" raw = bytearray() while True: char = stream.read(2) - if char in [b'', b'\x00\x00']: + if char in [b"", b"\x00\x00"]: break raw += char - return raw.decode('utf-16_le') + return raw.decode("utf-16_le") - sprite_name = read_utf16le(stream) - author_name = read_utf16le(stream) - author_credits_name = stream.read().split(b"\x00", 1)[0].decode() + # noinspection PyBroadException + try: + sprite_name = read_utf16le(stream) + author_name = read_utf16le(stream) + author_credits_name = stream.read().split(b"\x00", 1)[0].decode() - # Ignoring the Author Rom name for the time being. + # Ignoring the Author Rom name for the time being. - real_csum = sum(filedata) % 0x10000 - if real_csum != csum or real_csum ^ 0xFFFF != icsum: - logger.warning('ZSPR file has incorrect checksum. It may be corrupted.') + real_csum = sum(filedata) % 0x10000 + if real_csum != csum or real_csum ^ 0xFFFF != icsum: + logger.warning("ZSPR file has incorrect checksum. It may be corrupted.") - sprite = filedata[sprite_offset:sprite_offset + sprite_size] - palette = filedata[palette_offset:palette_offset + palette_size] + sprite = filedata[sprite_offset:sprite_offset + sprite_size] + palette = filedata[palette_offset:palette_offset + palette_size] - if len(sprite) != sprite_size or len(palette) != palette_size: - logger.error('Error parsing ZSPR file: Unexpected end of file') + if len(sprite) != sprite_size or len(palette) != palette_size: + logger.error("Error parsing ZSPR file: Unexpected end of file") + return None + + return sprite, palette, sprite_name, author_name, author_credits_name + + except Exception: + logger.exception("Error parsing ZSPR file") return None - return (sprite, palette, sprite_name, author_name, author_credits_name) - def decode_palette(self): - "Returns the palettes as an array of arrays of 15 colors" + """Returns the palettes as an array of arrays of 15 colors""" def array_chunk(arr, size): return list(zip(*[iter(arr)] * size))