remove remaining sprite data

This commit is contained in:
Fabian Dill 2021-04-27 07:19:53 +02:00
parent e04fbd1d77
commit b8c7d6a72f
4 changed files with 129 additions and 61 deletions

7
data/default.apsprite Normal file
View File

@ -0,0 +1,7 @@
author: Nintendo
data: null
game: A Link to the Past
min_format_version: 1
name: Link
format_version: 1
sprite_version: 1

Binary file not shown.

View File

@ -0,0 +1,7 @@
author: Nintendo
data: null
game: A Link to the Past
min_format_version: 1
name: Link
format_version: 1
sprite_version: 1

View File

@ -14,6 +14,7 @@ import subprocess
import threading
import xxtea
import concurrent.futures
import bsdiff4
from typing import Optional
from BaseClasses import CollectionState, Region
@ -22,10 +23,12 @@ from worlds.alttp.Shops import ShopType
from worlds.alttp.Dungeons import dungeon_music_addresses
from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address
from worlds.alttp.Text import MultiByteTextMapper, text_addresses, Credits, TextTable
from worlds.alttp.Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, \
from worlds.alttp.Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, \
Blind_texts, \
BombShop2_texts, junk_texts
from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, \
from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_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 output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_bundled
@ -41,6 +44,7 @@ except:
enemizer_logger = logging.getLogger("Enemizer")
class LocalRom(object):
def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None):
@ -428,8 +432,11 @@ def patch_enemizer(world, team: int, player: int, rom: LocalRom, enemizercli):
except OSError:
pass
tile_list_lock = threading.Lock()
_tile_collection_table = []
def _populate_tile_sets():
with tile_list_lock:
if not _tile_collection_table:
@ -442,6 +449,7 @@ def _populate_tile_sets():
for file in os.listdir(dir):
pool.submit(load_tileset_from_file, os.path.join(dir, file))
class TileSet:
def __init__(self, filename):
with open(filename, 'rt', encoding='utf-8-sig') as file:
@ -497,25 +505,23 @@ def _populate_sprite_table():
for file in os.listdir(dir):
pool.submit(load_sprite_from_file, os.path.join(dir, file))
class Sprite(object):
palette = (255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
89, 71, 54, 104, 59, 74, 10, 239, 18, 92, 42, 113, 21, 24, 122,
255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
89, 128, 105, 145, 118, 184, 38, 127, 67, 92, 42, 153, 17, 24, 122,
255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
89, 87, 16, 126, 69, 243, 109, 185, 126, 92, 42, 39, 34, 24, 122,
255, 127, 126, 35, 218, 17, 158, 54, 165, 20, 255, 1, 120, 16, 151,
61, 71, 54, 104, 59, 74, 10, 239, 18, 126, 86, 114, 24, 24, 122)
glove_palette = (246, 82, 118, 3)
class Sprite():
sprite_size = 28672
palette_size = 120
glove_size = 4
author_name: Optional[str] = None
def __init__(self, filename):
if not hasattr(Sprite, "palette"):
self.get_vanilla_sprite_data()
with open(filename, 'rb') as file:
filedata = bytearray(file.read())
filedata = file.read()
self.name = os.path.basename(filename)
self.valid = True
if len(filedata) == 0x7000:
if filename.endswith(".apsprite"):
self.from_ap_sprite(filedata)
elif len(filedata) == 0x7000:
# sprite file with graphics and without palette data
self.sprite = filedata[:0x7000]
elif len(filedata) == 0x7078:
@ -534,26 +540,70 @@ class Sprite(object):
self.palette = filedata[0xDD308:0xDD380]
self.glove_palette = filedata[0xDEDF5:0xDEDF9]
elif filedata.startswith(b'ZSPR'):
result = self.parse_zspr(filedata, 1)
if result is None:
self.valid = False
return
(sprite, palette, self.name, self.author_name) = result
if self.name == "":
self.name = os.path.split(filename)[1].split(".")[0]
if len(sprite) != 0x7000:
self.valid = False
return
self.sprite = sprite
if len(palette) == 0:
pass
elif len(palette) == 0x78:
self.palette = palette
elif len(palette) == 0x7C:
self.palette = palette[:0x78]
self.glove_palette = palette[0x78:]
else:
self.valid = False
self.from_zspr(filedata, filename)
else:
self.valid = False
def get_vanilla_sprite_data(self):
from Patch import get_base_rom_path
file_name = get_base_rom_path()
base_rom_bytes = bytes(read_rom(open(file_name, "rb")))
sprite = base_rom_bytes[0x80000:0x87000]
palette = base_rom_bytes[0xDD308:0xDD380]
glove_palette = base_rom_bytes[0xDEDF5:0xDEDF9]
Sprite.base_data = sprite + palette + 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:]
def to_ap_sprite(self, path):
from .. import Games
import yaml
payload = {"format_version": 1,
"min_format_version": 1,
"sprite_version": 1,
"name": self.name,
"author": self.author_name,
"game": Games.LTTP.value,
"data": self.get_delta()}
with open(path, "w") as f:
f.write(yaml.safe_dump(payload))
def get_delta(self):
modified_data = self.sprite + self.palette + self.glove_palette
return bsdiff4.diff(Sprite.base_data, modified_data)
def from_zspr(self, filedata, filename):
result = self.parse_zspr(filedata, 1)
if result is None:
self.valid = False
return
(sprite, palette, self.name, self.author_name) = result
if self.name == "":
self.name = os.path.split(filename)[1].split(".")[0]
if len(sprite) != 0x7000:
self.valid = False
return
self.sprite = sprite
if len(palette) == 0:
pass
elif len(palette) == 0x78:
self.palette = palette
elif len(palette) == 0x7C:
self.palette = palette[:0x78]
self.glove_palette = palette[0x78:]
else:
self.valid = False
@ -569,7 +619,7 @@ class Sprite(object):
@staticmethod
def default_link_sprite():
return Sprite(local_path('../../data', 'default.zspr'))
return Sprite(local_path('data', 'default.apsprite'))
def decode8(self, pos):
arr = [[0 for _ in range(8)] for _ in range(8)]
@ -603,12 +653,12 @@ class Sprite(object):
return arr
def parse_zspr(self, filedata, expected_kind):
logger = logging.getLogger('')
logger = logging.getLogger('ZSPR')
headerstr = "<4xBHHIHIHH6x"
headersize = struct.calcsize(headerstr)
if len(filedata) < headersize:
return None
(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)
if version not in [1]:
logger.error('Error parsing ZSPR file: Version %g not supported', version)
@ -657,7 +707,7 @@ class Sprite(object):
return pair[1] << 8 | pair[0]
def expand_color(i):
return ((i & 0x1F) * 8, (i >> 5 & 0x1F) * 8, (i >> 10 & 0x1F) * 8)
return (i & 0x1F) * 8, (i >> 5 & 0x1F) * 8, (i >> 10 & 0x1F) * 8
# turn palette data into a list of RGB tuples with 8 bit values
palette_as_colors = [expand_color(make_int16(chnk)) for chnk in array_chunk(self.palette, 2)]
@ -689,6 +739,7 @@ bonk_addresses = [0x4CF6C, 0x4CFBA, 0x4CFE0, 0x4CFFB, 0x4D018, 0x4D01B, 0x4D028,
0x4D3F8, 0x4D416, 0x4D420, 0x4D423, 0x4D42D, 0x4D449, 0x4D48C, 0x4D4D9, 0x4D4DC, 0x4D4E3,
0x4D504, 0x4D507, 0x4D55E, 0x4D56A]
def patch_rom(world, rom, player, team, enemized):
local_random = world.rom_seeds[player]
@ -751,7 +802,6 @@ def patch_rom(world, rom, player, team, enemized):
for music_address in music_addresses:
rom.write_byte(music_address, music)
if world.mapshuffle[player]:
rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
@ -1070,7 +1120,6 @@ def patch_rom(world, rom, player, team, enemized):
byte = int(rom.read_byte(address))
rom.write_byte(address, prize_replacements.get(byte, byte))
# Fill in item substitutions table
rom.write_bytes(0x184000, [
# original_item, limit, replacement_item, filler
@ -1146,9 +1195,12 @@ def patch_rom(world, rom, player, team, enemized):
# Set up requested clock settings
if world.clock_mode[player] in ['countdown-ohko', 'stopwatch', 'countdown']:
rom.write_int32(0x180200, world.red_clock_time[player] * 60 * 60) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, world.blue_clock_time[player] * 60 * 60) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208, world.green_clock_time[player] * 60 * 60) # green clock adjustment time (in frames, sint32)
rom.write_int32(0x180200,
world.red_clock_time[player] * 60 * 60) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204,
world.blue_clock_time[player] * 60 * 60) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208,
world.green_clock_time[player] * 60 * 60) # green clock adjustment time (in frames, sint32)
else:
rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32)
@ -1507,7 +1559,8 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_byte(0xEFD95, digging_game_rng)
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
rom.write_byte(0x1800A4, 0x01 if world.logic[player] != 'nologic' else 0x00) # enable POD EG fix
rom.write_byte(0x186383, 0x01 if world.glitch_triforce or world.logic[player] == 'nologic' else 0x00) # disable glitching to Triforce from Ganons Room
rom.write_byte(0x186383, 0x01 if world.glitch_triforce or world.logic[
player] == 'nologic' else 0x00) # disable glitching to Triforce from Ganons Room
rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill
# remove shield from uncle
@ -1579,7 +1632,6 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_byte(0x4BA1D, tile_set.get_len())
rom.write_bytes(0x4BA2A, tile_set.get_bytes())
write_strings(rom, world, player, team)
rom.write_byte(0x18637C, 1 if world.remote_items[player] else 0)
@ -1650,7 +1702,7 @@ def write_custom_shops(rom, world, player):
slot = 0 if shop.type == ShopType.TakeAny else index
if item is None:
break
if not item['item'] in item_table: # item not native to ALTTP
if not item['item'] in item_table: # item not native to ALTTP
item_code = 0x09 # Hammer
else:
item_code = ItemFactory(item['item'], player).code
@ -1691,7 +1743,8 @@ def hud_format_text(text):
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite: str, palettes_options,
world=None, player=1, allow_random_on_event=False, reduceflashing=False, triforcehud:str = None):
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
triforcehud: str = None):
local_random = random if not world else world.rom_seeds[player]
# enable instant item menu
@ -1716,22 +1769,22 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
else:
rom.write_byte(0x180048, 0x08)
# Reduce flashing by nopping out instructions
if reduceflashing:
rom.write_bytes(0x17E07, [0x06]) # reduce amount of colors changed, add this branch if we need to reduce more ""+ [0x80] + [(0x81-0x08)]""
rom.write_bytes(0x17EAB, [0xD0, 0x03, 0xA9, 0x40, 0x29, 0x60]) # nullifies aga lightning, cutscene, vitreous, bat, ether
rom.write_bytes(0x17E07, [
0x06]) # reduce amount of colors changed, add this branch if we need to reduce more ""+ [0x80] + [(0x81-0x08)]""
rom.write_bytes(0x17EAB,
[0xD0, 0x03, 0xA9, 0x40, 0x29, 0x60]) # nullifies aga lightning, cutscene, vitreous, bat, ether
# ONLY write to black values with this low pale blue to indicate flashing, that's IT. ""BNE + : LDA #$2940 : + : RTS""
rom.write_bytes(0x123FE, [0x72]) # set lightning flash in misery mire (and standard) to brightness 0x72
rom.write_bytes(0x3FA7B, [0x80, 0xac-0x7b]) # branch from palette writing lightning on death mountain
rom.write_byte(0x10817F, 0x01) # internal rom option
rom.write_bytes(0x123FE, [0x72]) # set lightning flash in misery mire (and standard) to brightness 0x72
rom.write_bytes(0x3FA7B, [0x80, 0xac - 0x7b]) # branch from palette writing lightning on death mountain
rom.write_byte(0x10817F, 0x01) # internal rom option
else:
rom.write_bytes(0x17E07, [0x00])
rom.write_bytes(0x17E07, [0x00])
rom.write_bytes(0x17EAB, [0x85, 0x00, 0x29, 0x1F, 0x00, 0x18])
rom.write_bytes(0x123FE, [0x32]) # original weather flash value
rom.write_bytes(0x3FA7B, [0xc2, 0x20]) # rep #$20
rom.write_byte(0x10817F, 0x00) # internal rom option
rom.write_bytes(0x123FE, [0x32]) # original weather flash value
rom.write_bytes(0x3FA7B, [0xc2, 0x20]) # rep #$20
rom.write_byte(0x10817F, 0x00) # internal rom option
rom.write_byte(0x18004B, 0x01 if quickswap else 0x00)
@ -1766,7 +1819,8 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
if triforcehud:
# set triforcehud
triforce_flag = (rom.read_byte(0x180167) & 0x80) | {'normal': 0x00, 'hide_goal': 0x01, 'hide_required': 0x02, 'hide_both': 0x03}[triforcehud]
triforce_flag = (rom.read_byte(0x180167) & 0x80) | \
{'normal': 0x00, 'hide_goal': 0x01, 'hide_required': 0x02, 'hide_both': 0x03}[triforcehud]
rom.write_byte(0x180167, triforce_flag)
if z3pr:
@ -2045,7 +2099,7 @@ def write_strings(rom, world, player, team):
f"\n ≥ Duh\n Oh carp\n{{CHOICE}}"
# Bottle Vendor hint
vendor_location = world.get_location("Bottle Merchant", player)
tt['bottle_vendor_choice'] = f"I gots {hint_text(vendor_location.item)}\nYous gots 100 rupees?"\
tt['bottle_vendor_choice'] = f"I gots {hint_text(vendor_location.item)}\nYous gots 100 rupees?" \
f"\n ≥ I want\n no way!\n{{CHOICE}}"
tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!'
@ -2394,7 +2448,7 @@ def set_inverted_mode(world, player, rom):
rom.write_byte(0xDC21D, 0x6B) # inverted mode flute activation (skip weathervane overlay)
rom.write_bytes(0x48DB3, [0xF8, 0x01]) # inverted mode (bird X)
rom.write_byte(0x48D5E, 0x01) # inverted mode (rock X)
rom.write_bytes(0x48CC1+36, bytes([0xF8]*12)) # (rock X)
rom.write_bytes(0x48CC1 + 36, bytes([0xF8] * 12)) # (rock X)
rom.write_int16s(snes_to_pc(0x02E849),
[0x0043, 0x0056, 0x0058, 0x006C, 0x006F, 0x0070, 0x007B, 0x007F, 0x001B]) # dw flute
rom.write_int16(snes_to_pc(0x02E8D5), 0x07C8)