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>
This commit is contained in:
parent
6d6111de2a
commit
b1ffbc49c9
|
@ -752,6 +752,7 @@ class SpriteSelector():
|
||||||
self.window['pady'] = 5
|
self.window['pady'] = 5
|
||||||
self.spritesPerRow = 32
|
self.spritesPerRow = 32
|
||||||
self.all_sprites = []
|
self.all_sprites = []
|
||||||
|
self.invalid_sprites = []
|
||||||
self.sprite_pool = spritePool
|
self.sprite_pool = spritePool
|
||||||
|
|
||||||
def open_custom_sprite_dir(_evt):
|
def open_custom_sprite_dir(_evt):
|
||||||
|
@ -833,6 +834,13 @@ class SpriteSelector():
|
||||||
self.window.focus()
|
self.window.focus()
|
||||||
tkinter_center_window(self.window)
|
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):
|
def remove_from_sprite_pool(self, button, spritename):
|
||||||
self.callback(("remove", spritename))
|
self.callback(("remove", spritename))
|
||||||
self.spritePoolButtons.buttons.remove(button)
|
self.spritePoolButtons.buttons.remove(button)
|
||||||
|
@ -897,7 +905,13 @@ class SpriteSelector():
|
||||||
sprites = []
|
sprites = []
|
||||||
|
|
||||||
for file in os.listdir(path):
|
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())
|
sprites.sort(key=lambda s: str.lower(s[1].name or "").strip())
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts
|
||||||
DeathMountain_texts, \
|
DeathMountain_texts, \
|
||||||
LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \
|
LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \
|
||||||
SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
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.Items import ItemFactory, item_table, item_name_groups, progression_items
|
||||||
from worlds.alttp.EntranceShuffle import door_addresses
|
from worlds.alttp.EntranceShuffle import door_addresses
|
||||||
from worlds.alttp.Options import smallkey_shuffle
|
from worlds.alttp.Options import smallkey_shuffle
|
||||||
|
@ -551,18 +551,22 @@ class Sprite():
|
||||||
Sprite.base_data = Sprite.sprite + Sprite.palette + Sprite.glove_palette
|
Sprite.base_data = Sprite.sprite + Sprite.palette + Sprite.glove_palette
|
||||||
|
|
||||||
def from_ap_sprite(self, filedata):
|
def from_ap_sprite(self, filedata):
|
||||||
filedata = filedata.decode("utf-8-sig")
|
# noinspection PyBroadException
|
||||||
import yaml
|
try:
|
||||||
obj = yaml.safe_load(filedata)
|
obj = parse_yaml(filedata.decode("utf-8-sig"))
|
||||||
if obj["min_format_version"] > 1:
|
if obj["min_format_version"] > 1:
|
||||||
raise Exception("Sprite file requires an updated reader.")
|
raise Exception("Sprite file requires an updated reader.")
|
||||||
self.author_name = obj["author"]
|
self.author_name = obj["author"]
|
||||||
self.name = obj["name"]
|
self.name = obj["name"]
|
||||||
if obj["data"]: # skip patching for vanilla content
|
if obj["data"]: # skip patching for vanilla content
|
||||||
data = bsdiff4.patch(Sprite.base_data, obj["data"])
|
data = bsdiff4.patch(Sprite.base_data, obj["data"])
|
||||||
self.sprite = data[:self.sprite_size]
|
self.sprite = data[:self.sprite_size]
|
||||||
self.palette = data[self.sprite_size:self.palette_size]
|
self.palette = data[self.sprite_size:self.palette_size]
|
||||||
self.glove_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
|
@property
|
||||||
def author_game_display(self) -> str:
|
def author_game_display(self) -> str:
|
||||||
|
@ -659,7 +663,7 @@ class Sprite():
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_zspr(filedata, expected_kind):
|
def parse_zspr(filedata, expected_kind):
|
||||||
logger = logging.getLogger('ZSPR')
|
logger = logging.getLogger("ZSPR")
|
||||||
headerstr = "<4xBHHIHIHH6x"
|
headerstr = "<4xBHHIHIHH6x"
|
||||||
headersize = struct.calcsize(headerstr)
|
headersize = struct.calcsize(headerstr)
|
||||||
if len(filedata) < headersize:
|
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(
|
version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind = struct.unpack_from(
|
||||||
headerstr, filedata)
|
headerstr, filedata)
|
||||||
if version not in [1]:
|
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
|
return None
|
||||||
if kind != expected_kind:
|
if kind != expected_kind:
|
||||||
return None
|
return None
|
||||||
|
@ -676,36 +680,42 @@ class Sprite():
|
||||||
stream.seek(headersize)
|
stream.seek(headersize)
|
||||||
|
|
||||||
def read_utf16le(stream):
|
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()
|
raw = bytearray()
|
||||||
while True:
|
while True:
|
||||||
char = stream.read(2)
|
char = stream.read(2)
|
||||||
if char in [b'', b'\x00\x00']:
|
if char in [b"", b"\x00\x00"]:
|
||||||
break
|
break
|
||||||
raw += char
|
raw += char
|
||||||
return raw.decode('utf-16_le')
|
return raw.decode("utf-16_le")
|
||||||
|
|
||||||
sprite_name = read_utf16le(stream)
|
# noinspection PyBroadException
|
||||||
author_name = read_utf16le(stream)
|
try:
|
||||||
author_credits_name = stream.read().split(b"\x00", 1)[0].decode()
|
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
|
real_csum = sum(filedata) % 0x10000
|
||||||
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
|
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
|
||||||
logger.warning('ZSPR file has incorrect checksum. It may be corrupted.')
|
logger.warning("ZSPR file has incorrect checksum. It may be corrupted.")
|
||||||
|
|
||||||
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
|
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
|
||||||
palette = filedata[palette_offset:palette_offset + palette_size]
|
palette = filedata[palette_offset:palette_offset + palette_size]
|
||||||
|
|
||||||
if len(sprite) != sprite_size or len(palette) != palette_size:
|
if len(sprite) != sprite_size or len(palette) != palette_size:
|
||||||
logger.error('Error parsing ZSPR file: Unexpected end of file')
|
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 None
|
||||||
|
|
||||||
return (sprite, palette, sprite_name, author_name, author_credits_name)
|
|
||||||
|
|
||||||
def decode_palette(self):
|
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):
|
def array_chunk(arr, size):
|
||||||
return list(zip(*[iter(arr)] * size))
|
return list(zip(*[iter(arr)] * size))
|
||||||
|
|
Loading…
Reference in New Issue