mirror of
https://github.com/snesrev/zelda3.git
synced 2025-07-23 12:23:04 +02:00
641 lines
22 KiB
Python
641 lines
22 KiB
Python
from PIL import Image
|
|
import sprite_sheet_info
|
|
import util
|
|
from util import get_bytes, get_words, get_byte, cache
|
|
import array
|
|
import tables
|
|
import sys
|
|
|
|
override_armor_palette = None
|
|
#override_armor_palette = [0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x3647, 0x3b68, 0xa4a, 0x12ef, 0x2a5c, 0x1571, 0x7a18,
|
|
# 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x6980, 0x7691, 0x26b8, 0x437f, 0x2a5c, 0x1199, 0x7a18,
|
|
# 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x1057, 0x457e, 0x6df3, 0xfeb9, 0x2a5c, 0x2227, 0x7a18,
|
|
# 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x3d97, 0x3647, 0x3b68, 0xa4a, 0x12ef, 0x567e, 0x1571, 0x7a18,
|
|
# 0, 0x0EFA, 0x7DD1, 0, 0x7F1A, 0x7F1A,0, 0x716E, 0x7DD1, 0x40A7, 0x7DD1, 0x40A7, 0x48E9, 0x50CF, 0x7FFF]
|
|
|
|
|
|
def save_as_png(dimensions, data, fname, palette = None):
|
|
img = Image.new('L' if palette == None else 'P', dimensions)
|
|
img.putdata(data)
|
|
if palette != None:
|
|
img.putpalette(palette)
|
|
img.save(fname)
|
|
|
|
def save_as_24bpp_png(dimensions, data, fname):
|
|
img = Image.new("RGB", dimensions)
|
|
img.putdata(data)
|
|
img.save(fname)
|
|
|
|
@cache
|
|
def decode_2bit_tileset(tileset, height = 32, base = 0):
|
|
data = util.decomp(tables.kCompSpritePtrs[tileset], get_byte, False)
|
|
assert len(data) == 0x400 * height // 32
|
|
dst = bytearray(128*height)
|
|
def decode_2bit(offs, toffs, base):
|
|
for y in range(8):
|
|
d0, d1 = data[offs + y * 2], data[offs + y * 2 + 1]
|
|
for x in range(8):
|
|
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2
|
|
dst[toffs + y * 128 + (7 - x)] = t + base
|
|
for i in range(16*height//8):
|
|
x = i % 16
|
|
y = i // 16
|
|
decode_2bit(i * 16, x * 8 + y * 8 * 128, base[i] if isinstance(base, tuple) else base)
|
|
return dst
|
|
|
|
def is_high_3bit_tileset(tileset):
|
|
# is 0x5c, 0x5e, 0x5f included or not?
|
|
return tileset in (0x52, 0x53, 0x5a, 0x5b, 0x5c, 0x5e, 0x5f)
|
|
|
|
def get_unpacked_snes_tileset(tileset):
|
|
if tileset < 12:
|
|
return get_bytes(tables.kCompSpritePtrs[tileset], 0x600)
|
|
else:
|
|
return util.decomp(tables.kCompSpritePtrs[tileset], get_byte, False)
|
|
|
|
@cache
|
|
def decode_3bit_tileset(tileset):
|
|
data = get_unpacked_snes_tileset(tileset)
|
|
assert len(data) == 0x600
|
|
base = 8 if is_high_3bit_tileset(tileset) else 0
|
|
height = 32
|
|
dst = bytearray(128*height)
|
|
def decode_3bit(offs, toffs):
|
|
for y in range(8):
|
|
d0, d1, d2 = data[offs + y * 2], data[offs + y * 2 + 1], data[offs + y + 16]
|
|
for x in range(8):
|
|
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2 + ((d2 >> x) & 1) * 4
|
|
dst[toffs + y * 128 + (7 - x)] = base + t
|
|
for i in range(16*height//8):
|
|
x, y = i % 16, i // 16
|
|
decode_3bit(i * 24, x * 8 + y * 8 * 128)
|
|
return dst
|
|
|
|
def decode_4bit_tileset_link():
|
|
height = 448
|
|
data = get_bytes(0x108000, 0x800 * height // 32) # only link sprites for now
|
|
dst = bytearray(128*height)
|
|
def decode_4bit(offs, toffs):
|
|
for y in range(8):
|
|
d0, d1, d2, d3 = data[offs + y * 2 + 0], data[offs + y * 2 + 1], data[offs + y * 2 + 16], data[offs + y * 2 + 17]
|
|
for x in range(8):
|
|
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2 + ((d2 >> x) & 1) * 4 + ((d3 >> x) & 1) * 8
|
|
dst[toffs + y * 128 + (7 - x)] = t
|
|
for i in range(16*height//8):
|
|
x, y = i % 16, i // 16
|
|
decode_4bit(i * 32, x * 8 + y * 8 * 128)
|
|
return dst
|
|
|
|
def convert_snes_palette(v):
|
|
rr=bytearray()
|
|
for x in v:
|
|
r, g, b = x & 0x1f, x >> 5 & 0x1f, x >> 10 & 0x1f
|
|
rr.extend((r << 3 | r >> 2, g << 3 | g >> 2, b << 3 | b >> 2))
|
|
return rr
|
|
|
|
def convert_int24_palette_to_bytes(v):
|
|
rr=bytearray()
|
|
for x in v:
|
|
rr.extend((x & 0xff, x >> 8 & 0xff, x >> 16 & 0xff))
|
|
return rr
|
|
|
|
def convert_snes_palette_to_int(v):
|
|
rr=[]
|
|
for x in v:
|
|
r, g, b = x & 0x1f, x >> 5 & 0x1f, x >> 10 & 0x1f
|
|
rr.append((r << 3 | r >> 2) | (g << 3 | g >> 2) << 8 | (b << 3 | b >> 2) << 16)
|
|
return rr
|
|
|
|
def decode_link_sprites():
|
|
kLinkPalette = [0, 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x3647, 0x3b68, 0xa4a, 0x12ef, 0x2a5c, 0x1571, 0x7a18]
|
|
save_as_png((128, 448), decode_4bit_tileset_link(), 'linksprite.png', convert_snes_palette(kLinkPalette))
|
|
|
|
def get_hud_snes_palette():
|
|
hud_palette = get_words(0x9BD660, 64)
|
|
palette = [(31 << 10 | 31) for i in range(256)]
|
|
for i in range(16):
|
|
for j in range(1, 4):
|
|
palette[i * 16 + j] = hud_palette[i * 4 + j]
|
|
return palette
|
|
|
|
def decode_hud_icons():
|
|
class PaletteUsage:
|
|
def __init__(self):
|
|
self.data = open('palette_usage.bin', 'rb').read()
|
|
def get(self, icon):
|
|
usage = self.data[icon]
|
|
for j in range(8):
|
|
if usage & (1 << j):
|
|
return j
|
|
return 0
|
|
pu = PaletteUsage()
|
|
dst = bytearray()
|
|
for slot, image_set in enumerate([106, 107, 105]):
|
|
tbase = tuple([pu.get(slot * 128 + i) * 16 for i in range(128)])
|
|
dst += decode_2bit_tileset(image_set, height = 64, base = tbase)
|
|
|
|
save_as_png((128, 64 * 3), dst, 'hud_icons.png', convert_snes_palette(get_hud_snes_palette()[:128]))
|
|
|
|
def get_pt_remapper():
|
|
b = util.ROM.get_bytes(0x8EFC09, 121 * 3)
|
|
d = {}
|
|
for i in range(121):
|
|
ch = (i & 0xf) | (i << 1) & 0xe0
|
|
d[ch] = b[i*3+0]
|
|
d[ch|0x10] = b[i*3+1]
|
|
return d
|
|
|
|
kFontTypes = {
|
|
'us' : (0x8e8000, 256, 'font.png', (0x8ECADF, 99)),
|
|
'de' : (0xCC6E8, 256, 'font_de.png', (0x8CDECF, 112)),
|
|
'fr' : (0xCC6E8, 256, 'font_fr.png', (0x8CDEAF, 112)),
|
|
'fr-c' : (0xCD078, 256, 'font_fr_c.png', (0x8CE83F, 112)),
|
|
'en' : (0x8E8000, 256, 'font_en.png', (0x8ECAFF, 102)),
|
|
'es' : (0x8e8000, 256, 'font_es.png', (0x8ECADF, 99)),
|
|
'pl' : (0x8e8000, 256, 'font_pl.png', (0x8ECADF, 99)),
|
|
'pt' : (0x8e8000, 256, 'font_pt.png', (0x8ECADF, 121)),
|
|
'redux': (0x8e8000, 256, 'font_redux.png', (0x8ECADF, 99)),
|
|
'nl': (0x8e8000, 256, 'font_nl.png', (0x8ECADF, 99)),
|
|
'sv': (0x8e8000, 256, 'font_sv.png', (0x8ECADF, 99)),
|
|
}
|
|
|
|
def decode_font():
|
|
lang = util.ROM.language
|
|
def decomp_one_spr_2bit(data, offs, target, toffs, pitch, palette_base):
|
|
for y in range(8):
|
|
d0, d1 = data[offs + y * 2], data[offs + y * 2 + 1]
|
|
for x in range(8):
|
|
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2
|
|
target[toffs + y * pitch + (7 - x)] = t + palette_base
|
|
ft = kFontTypes[lang]
|
|
if lang == 'pt':
|
|
W = util.ROM.get_bytes(0x8EFC09, 121 * 3)
|
|
W = [W[i*3+2] for i in range(121)]
|
|
remapper = get_pt_remapper()
|
|
else:
|
|
W = get_bytes(*ft[3])
|
|
remapper = {}
|
|
w = 128 + 15
|
|
hi = ft[1] // 32
|
|
h = hi * 17
|
|
data = get_bytes(ft[0], ft[1] * 16)
|
|
dst = bytearray(w * h)
|
|
|
|
for i in range(ft[1]):
|
|
x, y = i % 16, i // 16
|
|
pal_base = 6 * 16
|
|
base_offs = x * 9 + (y * 8 + (y >> 1)) * w
|
|
decomp_one_spr_2bit(data, remapper.get(i, i) * 16, dst, base_offs + w, w, pal_base)
|
|
if (y & 1) == 0:
|
|
j = (y >> 1) * 16 + x
|
|
if j < len(W):
|
|
dst[base_offs + W[j] - 1] = 255
|
|
pal = convert_snes_palette(get_hud_snes_palette()[:128])
|
|
pal.extend([0] * 384)
|
|
pal[0], pal[1], pal[2] = 192, 192, 192
|
|
pal[255*3+0], pal[255*3+1], pal[255*3+2] = 128, 128, 128
|
|
save_as_png((w, h), dst, ft[2], pal)
|
|
if lang != 'pt':
|
|
assert (data, W) == encode_font_from_png(lang)
|
|
|
|
def encode_font_from_png(lang):
|
|
font_data = Image.open(kFontTypes[lang][2]).tobytes()
|
|
def encode_one_spr_2bit(data, offs, target, toffs, pitch):
|
|
for y in range(8):
|
|
d0, d1 = 0, 0
|
|
for x in range(8):
|
|
pixel = data[offs + y * pitch + 7 - x]
|
|
d0 |= (pixel & 1) << x
|
|
d1 |= ((pixel >> 1) & 1) << x
|
|
target[toffs + y * 2 + 0], target[toffs + y * 2 + 1] = d0, d1
|
|
w = 128 + 15
|
|
dst = bytearray(256 * 16)
|
|
def get_width(base_offs):
|
|
for i in range(8):
|
|
if font_data[base_offs + i] == 255:
|
|
break
|
|
return i + 1
|
|
W = bytearray()
|
|
for i in range(256):
|
|
x, y = i % 16, i // 16
|
|
base_offs = x * 9 + (y * 8 + (y >> 1)) * w
|
|
if (y & 1) == 0:
|
|
W.append(get_width(base_offs))
|
|
encode_one_spr_2bit(font_data, base_offs + w, dst, i * 16, w)
|
|
chars_per_lang = kFontTypes[lang][3][1]
|
|
return dst, W[:chars_per_lang]
|
|
|
|
# Returns the dungeon palette for the specified palette index
|
|
# 0 = lightworld, 1 = darkworld, 2 = dungeon
|
|
@cache
|
|
def get_palette_subidx(palset_idx, dungeon_or_ow, which_palette):
|
|
spmain = 1 if (dungeon_or_ow == 1) else 0
|
|
if dungeon_or_ow == 2:
|
|
main, sp0l, sp5l, sp6l = tables.kDungPalinfos[palset_idx]
|
|
sp0r = (main // 2) + 11
|
|
sp6r = 10
|
|
else:
|
|
sp5l, sp6l = tables.kOwSprPalInfo[palset_idx * 2 : palset_idx * 2 + 2]
|
|
sp0l = 3 if dungeon_or_ow == 1 else 1
|
|
sp0r = 9 if dungeon_or_ow == 1 else 7
|
|
sp6r = 8 if dungeon_or_ow == 1 else 6
|
|
pal_defs = (sp0l, sp0r,
|
|
spmain, None, spmain, None, spmain, None, None, None,
|
|
sp5l, None,
|
|
sp6l, sp6r,
|
|
None, None)
|
|
return pal_defs[which_palette]
|
|
|
|
@cache
|
|
def get_palette_subset(pal_idx, j):
|
|
if j == None: j = 0
|
|
pal = [0] * 7
|
|
if pal_idx == 0: # 0
|
|
kPalette_SpriteAux3 = get_words(0x9BD39E, 84)
|
|
return kPalette_SpriteAux3[j * 7 : j * 7 + 7]
|
|
if pal_idx == 1: # 0R
|
|
if j < 11:
|
|
kPalette_MiscSprite = get_words(0x9BD446, 77)
|
|
return kPalette_MiscSprite[j * 7 : j * 7 + 7]
|
|
else:
|
|
kPalette_DungBgMain = get_words(0x9BD734, 1800)
|
|
return kPalette_DungBgMain[(j - 11) * 90 : (j - 11) * 90 + 7]
|
|
if pal_idx in (2, 3, 4, 5, 6, 7, 8, 9):
|
|
o = j * 60 + ((pal_idx - 2) >> 1) * 15 + (pal_idx & 1) * 8
|
|
kPalette_MainSpr = get_words(0x9BD218, 120)
|
|
return kPalette_MainSpr[o : o + 7]
|
|
if pal_idx in (10, 12):
|
|
kPalette_SpriteAux1 = get_words(0x9BD4E0, 168)
|
|
return kPalette_SpriteAux1[j * 7 : j * 7 + 7]
|
|
assert pal_idx != 11
|
|
if pal_idx == 13:
|
|
kPalette_MiscSprite = get_words(0x9BD446, 77)
|
|
return kPalette_MiscSprite[j * 7 : j * 7 + 7]
|
|
elif pal_idx == 14:
|
|
kPalette_ArmorAndGloves = get_words(0x9BD308, 75)
|
|
return kPalette_ArmorAndGloves[1:8]
|
|
elif pal_idx == 15:
|
|
kPalette_ArmorAndGloves = get_words(0x9BD308, 75)
|
|
return kPalette_ArmorAndGloves[9:16]
|
|
|
|
@cache
|
|
def get_full_palette(pal_idx, pal_subidx):
|
|
rv = []
|
|
if pal_idx & 1:
|
|
rv.extend([0x00fe00] * 9)
|
|
else:
|
|
rv.extend([0x00fe00] * 1)
|
|
rv.extend(convert_snes_palette_to_int(get_palette_subset(pal_idx, pal_subidx)))
|
|
rv += [0xe000e0] * (256 - len(rv))
|
|
# change the transparent to teal #008080
|
|
for i in range(0, 128, 8):
|
|
rv[i] = 0x808000
|
|
rv[251] = 0xe0c0c0 # blueish text
|
|
rv[252] = 0xc0c0c0 # palette text
|
|
rv[253] = 0xf0f0f0 # unallocated
|
|
rv[254] = 0x404040 # lines
|
|
rv[255] = 0xe0e0e0 # bg
|
|
return rv
|
|
|
|
@cache
|
|
def get_font_3x5():
|
|
return Image.open('../other/3x5_font.png').tobytes()
|
|
|
|
def draw_letter3x5(dst, dst_pitch, dx, dy, ch, color):
|
|
font = get_font_3x5()
|
|
dst_offs = dy * dst_pitch + dx
|
|
src_offs = (ord(ch) & 31) * 4 + (ord(ch) // 32) * 6 * 128
|
|
for y in range(6):
|
|
for x in range(3):
|
|
if font[src_offs + y * 128 + x] == 0:
|
|
dst[dst_offs + y * dst_pitch + x] = color
|
|
|
|
def draw_string3x5(dst, dst_pitch, dx, dy, s, color):
|
|
for ch in s:
|
|
draw_letter3x5(dst, dst_pitch, dx, dy, ch, color)
|
|
dx += 4
|
|
|
|
def convert_to_24bpp(data, palette):
|
|
out = [0] * len(data)
|
|
for i in range(len(data)):
|
|
out[i] = palette[data[i]]
|
|
return out
|
|
|
|
def concat_byte_arrays(ar):
|
|
r = bytearray()
|
|
for a in ar:
|
|
r += a
|
|
return r
|
|
|
|
def fixup_sprite_set_entry(e, e_prev):
|
|
sprite_index = int(e.name[:2], 16) if e.name[0] != 'X' else 10000
|
|
|
|
if e.dungeon_or_ow != 3:
|
|
e.tileset = tables.kSpriteTilesets[e.tileset + (64 if e.dungeon_or_ow == 2 else 0)][e.ss_idx]
|
|
e.high_palette = is_high_3bit_tileset(e.tileset)
|
|
e.skip_header = False
|
|
if e.pal_base == None:
|
|
if sprite_index < len(tables.kSpriteInit_Flags3):
|
|
e.pal_base = (tables.kSpriteInit_Flags3[sprite_index] >> 1) & 7
|
|
else:
|
|
e.pal_base = 4 # just default to some palette
|
|
|
|
e.pal_idx = e.pal_base * 2 + e.high_palette
|
|
e.pal_subidx = get_palette_subidx(e.palset_idx, e.dungeon_or_ow, e.pal_idx)
|
|
|
|
if e_prev != None and e_prev.name == e.name and e_prev.pal_idx == e.pal_idx and e_prev.pal_subidx == e.pal_subidx:
|
|
e.skip_header = True
|
|
|
|
# the (pal_idx, pal_subidx) tuple identifies the palette
|
|
e.encoded_id = e.pal_base | e.tileset << 3 | e.skip_header << 10 | (e.pal_subidx or 0) << 11
|
|
e.encoded_id = e.encoded_id << 8 | ((e.encoded_id + 41) % 255)
|
|
|
|
|
|
BIGW = 148
|
|
def save_sprite_set_entry(finaldst, e, master_tilesheets = None):
|
|
empty_253 = [253] * 8
|
|
def fill_with_empty(dst, dst_offs):
|
|
for y in range(8):
|
|
o = dst_offs + y * BIGW
|
|
dst[o : o + 8] = empty_253
|
|
|
|
def copy_8x8(dst, dst_offs, src, src_offs, base):
|
|
for y in range(8):
|
|
for x in range(8):
|
|
dst[dst_offs + y * BIGW + x] = src[src_offs + y * 128 + x] + base
|
|
|
|
def hline(dst, x1, x2, y, color):
|
|
for x in range(x1, x2 + 1):
|
|
dst[y * BIGW + x] = color
|
|
|
|
def vline(dst, x, y1, y2, color):
|
|
for y in range(y1, y2 + 1):
|
|
dst[y * BIGW + x] = color
|
|
|
|
def fillrect(dst, x1, x2, y1, y2, color):
|
|
for y in range(y1, y2 + 1):
|
|
hline(dst, x1, x2, y, color)
|
|
|
|
def encode_id(dst, x, y, v):
|
|
while v:
|
|
if v & 1:
|
|
dst[y * BIGW + x] = 253
|
|
x -= 1
|
|
v >>= 1
|
|
|
|
palette = get_full_palette(e.pal_idx, e.pal_subidx)
|
|
|
|
if not e.skip_header:
|
|
pal_subidx = e.pal_subidx
|
|
if e.high_palette:
|
|
pal_name = '%sR' % e.pal_base
|
|
else:
|
|
pal_name = '%s' % e.pal_base
|
|
if e.pal_base in (1, 2, 3):
|
|
assert e.pal_subidx in (0, 1)
|
|
pal_subidx = 'LW' if pal_subidx == 0 else 'DW'
|
|
if pal_subidx != None:
|
|
pal_name = '%s-%s' % (pal_name, pal_subidx)
|
|
|
|
header = bytearray([255] * BIGW * 9)
|
|
draw_string3x5(header, BIGW, 1, 3, e.name[:22], 254)
|
|
|
|
for i in range(7):
|
|
xx = BIGW - 37 + i * 5 - 9
|
|
fillrect(header, xx, xx + 4, 3, 7, i + (9 if e.high_palette else 1))
|
|
|
|
draw_string3x5(header, BIGW, BIGW - 9, 3, '%2s' % e.tileset, 252)
|
|
draw_string3x5(header, BIGW, BIGW - 45 - 1 - len(pal_name) * 4, 3, pal_name, 252)
|
|
finaldst.extend(convert_to_24bpp(header, palette))
|
|
|
|
bigdst = bytearray([255] * BIGW * 36)
|
|
hline(bigdst, 1, 137, 0, 254)
|
|
hline(bigdst, 1, 137, 34, 254)
|
|
vline(bigdst, 1, 0, 34, 254)
|
|
vline(bigdst, 137, 0, 34, 254)
|
|
|
|
encode_id(bigdst, 137, 35, e.encoded_id << 9 | 0x55)
|
|
|
|
src = decode_3bit_tileset(e.tileset) # returns 128x64
|
|
|
|
for i in range(16*4):
|
|
x, y = i % 16, i // 16
|
|
dst_offs = (y * 8 + 1 + (y >> 1)) * BIGW + x * 8 + 2 + (x >> 1)
|
|
if x & 8: dst_offs
|
|
m = e.matrix[y][x]
|
|
if m == '.':
|
|
fill_with_empty(bigdst, dst_offs)
|
|
else:
|
|
copy_8x8(bigdst, dst_offs, src, x * 8 + y * 8 * 128, 0)
|
|
if master_tilesheets:
|
|
master_tilesheets.insert(e.tileset, src, x, y, palette, 0)
|
|
|
|
# display vram addresses
|
|
draw_string3x5(bigdst, BIGW, BIGW - 9, 4, '%Xx' % (e.ss_idx * 0x4), 251)
|
|
draw_string3x5(bigdst, BIGW, BIGW - 9, 4 + 17, '%Xx' % (e.ss_idx * 0x4 + 2), 251)
|
|
|
|
# collapse unused matrix sections
|
|
if all(m=='.' for m in e.matrix[0]) and all(m=='.' for m in e.matrix[1]):
|
|
del bigdst[1*BIGW:17*BIGW]
|
|
elif all(m=='.' for m in e.matrix[2]) and all(m=='.' for m in e.matrix[3]):
|
|
del bigdst[18*BIGW:34*BIGW]
|
|
|
|
if e.skip_header:
|
|
draw_string3x5(bigdst, BIGW, BIGW - 9, len(bigdst)//BIGW - 6, '%.2d' % e.tileset, 252)
|
|
|
|
finaldst.extend(convert_to_24bpp(bigdst, palette))
|
|
|
|
def fixup_entries():
|
|
e_prev = None
|
|
for e in sprite_sheet_info.entries:
|
|
fixup_sprite_set_entry(e, e_prev)
|
|
e_prev = e
|
|
|
|
class MasterTilesheets:
|
|
def __init__(self):
|
|
self.sheets = {}
|
|
|
|
def insert(self, tileset, src, xp, yp, palette, pal_base):
|
|
sheet = self.sheets.get(tileset)
|
|
if sheet == None:
|
|
sheet = (array.array('I', [0x00f000] * 128 * 32), None)
|
|
self.sheets[tileset] = sheet
|
|
sheet24 = sheet[0]
|
|
for y in range(yp * 8, yp * 8 + 8):
|
|
for x in range(xp * 8, xp * 8 + 8):
|
|
o = y * 128 + x
|
|
sheet24[o] = palette[pal_base + src[o]]
|
|
|
|
def add_verify_8x8(self, tileset, pal_lut, img_data, pitch, dst_pos, src_pos):
|
|
# add to the master sheet
|
|
sheet = self.sheets.get(tileset)
|
|
if sheet == None:
|
|
sheet = (array.array('I', [0x00f000] * 128 * 32), bytearray([248] * 128 * 32))
|
|
self.sheets[tileset] = sheet
|
|
sheet24, sheet8 = sheet[0], sheet[1]
|
|
for y in range(8):
|
|
for x in range(8):
|
|
pixel = img_data[src_pos + y * pitch + x]
|
|
o = dst_pos + y * 128 + x
|
|
sheet24[o] = pixel
|
|
pixel8 = pal_lut.get(pixel)
|
|
if pixel8 == None:
|
|
raise Exception('Pixel #%.6x not found' % pixel)
|
|
if sheet8[o] != 248 and pixel8 != sheet8[o]:
|
|
raise Exception('Pixel has more than one value')
|
|
sheet8[o] = pixel8
|
|
|
|
def save_to_all_sheets(self, save_also_8bit = False):
|
|
rv = array.array('I')
|
|
if save_also_8bit:
|
|
rv8 = bytearray()
|
|
|
|
def extend_with_string_line(text):
|
|
header = array.array('I', [0xf0f0f0] * 128 * 8)
|
|
draw_string3x5(header, 128, 1, 2, text, 0x404040)
|
|
rv.extend(header)
|
|
if save_also_8bit:
|
|
header8 = bytearray([255] * 128 * 8)
|
|
draw_string3x5(header8, 128, 1, 2, text, 254)
|
|
rv8.extend(header8)
|
|
extend_with_string_line('AUTO GENERATED DO NOT EDIT')
|
|
kk = 0
|
|
for k in sorted(self.sheets.keys()):
|
|
extend_with_string_line('Sheet %d' % k)
|
|
rv.extend(self.sheets[k][0])
|
|
if save_also_8bit:
|
|
rv8.extend(self.sheets[k][1])
|
|
if k != kk:
|
|
print('Missing %d' % kk)
|
|
kk = k + 1
|
|
if save_also_8bit:
|
|
palette8 = get_full_palette(4, 0)
|
|
for i, v in enumerate(convert_snes_palette_to_int(get_palette_subset(5, 0))):
|
|
palette8[i + 9] = v
|
|
palette8[248] = 0x00f000
|
|
save_as_png((128, len(rv)//128), rv8, 'sprites/all_sheets_8bit.png', palette = convert_int24_palette_to_bytes(palette8))
|
|
self.verify_identical_to_snes()
|
|
save_as_24bpp_png((128, len(rv)//128), rv, 'sprites/all_sheets.png')
|
|
|
|
def verify_identical_to_snes(self):
|
|
for tileset in range(103):
|
|
a = self.encode_sheet_in_snes_format(tileset)
|
|
b = get_unpacked_snes_tileset(tileset)
|
|
if a != b:
|
|
print('Sheet %d mismatch' % tileset)
|
|
break
|
|
|
|
# convert a tileset to the 8bit snes format, 24 bytes per tile
|
|
def encode_sheet_in_snes_format(self, tileset):
|
|
sheet8 = self.sheets[tileset][1]
|
|
result = bytearray(24 * 16 * 4)
|
|
def encode_into(dst_pos, src_pos):
|
|
for y in range(8):
|
|
for x in range(8):
|
|
b = sheet8[src_pos + 128 * y + 7 - x]
|
|
result[dst_pos+2*y] |= (b & 1) << x
|
|
result[dst_pos+2*y+1] |= ((b & 2) >> 1) << x
|
|
result[dst_pos+y+16] |= ((b & 4) >> 2) << x
|
|
for y in range(4):
|
|
for x in range(16):
|
|
encode_into((y * 16 + x) * 24, y * 8 * 128 + x * 8)
|
|
return result
|
|
|
|
def decode_sprite_sheets():
|
|
master_tilesheets = MasterTilesheets()
|
|
|
|
fixup_entries()
|
|
for char in "0123456789ABCDEFX":
|
|
dst = []
|
|
for e in sprite_sheet_info.entries:
|
|
if e.name[0].upper() == char:
|
|
save_sprite_set_entry(dst, e, master_tilesheets)
|
|
if len(dst):
|
|
save_as_24bpp_png((BIGW, len(dst) // BIGW), dst, 'sprites/sprites_%s.png' % char)
|
|
master_tilesheets.save_to_all_sheets()
|
|
|
|
def load_sprite_sheets():
|
|
master_tilesheets = MasterTilesheets()
|
|
for char in "0123456789ABCDEFX":
|
|
img = Image.open('sprites/sprites_%s.png' % char)
|
|
img_size = img.size
|
|
img_data = array.array('I', (a[0] | a[1] << 8 | a[2] << 16 for a in img.getdata()))
|
|
pitch = img.size[0]
|
|
|
|
def find_next_tag(start_pos):
|
|
for i in range(start_pos, len(img_data) - pitch * 2, pitch if start_pos != 0 else 1):
|
|
if img_data[i+0] == 0x404040 and img_data[i+pitch] == 0x404040 and \
|
|
img_data[i+pitch * 2 + 0] == 0xf0f0f0 and img_data[i+pitch * 2 - 1] == 0xe0e0e0 and \
|
|
img_data[i+pitch * 2 - 2] == 0xf0f0f0 and img_data[i+pitch * 2 - 3] == 0xe0e0e0 and \
|
|
img_data[i+pitch * 2 - 4] == 0xf0f0f0 and img_data[i+pitch * 2 - 5] == 0xe0e0e0 and \
|
|
img_data[i+pitch * 2 - 6] == 0xf0f0f0 and img_data[i+pitch * 2 - 7] == 0xe0e0e0:
|
|
return i + pitch * 2
|
|
else:
|
|
return None
|
|
|
|
def decode_tag(pos):
|
|
r = 0
|
|
for i in range(64):
|
|
v = img_data[pos - 63 + i]
|
|
assert v in (0xe0e0e0, 0xf0f0f0)
|
|
r = r * 2 + (v == 0xf0f0f0)
|
|
if (r & 0x1ff) != 0x55:
|
|
raise Exception('Invalid tag')
|
|
r >>= 9
|
|
if (((r >> 8) + 41) % 255) != (r & 0xff):
|
|
raise Exception('Invalid checksum')
|
|
r >>= 8
|
|
return r & 7, (r >> 3) & 127, (r >> 10) & 1, (r >> 11) & 31
|
|
|
|
def determine_image_rects(tag_pos):
|
|
h = 1
|
|
while (img_data[tag_pos - pitch * (h + 1)] == 0x404040):
|
|
h += 1
|
|
if h == 19:
|
|
if img_data[tag_pos - pitch * 2 - 1] == 0xe0e0e0:
|
|
image_rects = ((0, tag_pos - 135 - pitch * 18), )
|
|
elif img_data[tag_pos - pitch * 18 - 1] == 0xe0e0e0:
|
|
image_rects = ((1, tag_pos - 135 - pitch * 17), )
|
|
else:
|
|
raise Exception('cant uncompact')
|
|
elif h == 35:
|
|
image_rects = ((0, tag_pos - 135 - pitch * 34), (1, tag_pos - 135 - pitch * 17))
|
|
else:
|
|
raise Exception('height not found')
|
|
return image_rects, h
|
|
|
|
def get_pal_lut(tag_pos, is_high):
|
|
# read palette colors from the image pixels
|
|
pal_lut = {img_data[tag_pos + 5 * i] : i + (9 if is_high else 1) for i in range(7)}
|
|
pal_lut[0x808000] = 0 # transparent color
|
|
return pal_lut
|
|
|
|
def is_empty(pos):
|
|
return all(img_data[pos+y*pitch+x] == 0xf0f0f0 for x in range(8) for y in range(8))
|
|
|
|
pal_lut = None
|
|
tag_pos = 0
|
|
while True:
|
|
tag_pos = find_next_tag(tag_pos)
|
|
if tag_pos == None:
|
|
break
|
|
pal_base, tileset, headerless, pal_subidx = decode_tag(tag_pos)
|
|
image_rects, box_height = determine_image_rects(tag_pos)
|
|
if not headerless:
|
|
pal_lut = get_pal_lut(tag_pos - pitch * (box_height + 3) - 34, is_high_3bit_tileset(tileset))
|
|
for idx, sheet_pos in image_rects:
|
|
for y in range(2):
|
|
for x in range(16):
|
|
src_pos = sheet_pos + (y * 8 + (y >> 1)) * pitch + (x * 8 + (x >> 1))
|
|
dst_pos = (y + idx * 2) * 8 * 128 + x * 8
|
|
if not is_empty(src_pos):
|
|
master_tilesheets.add_verify_8x8(tileset, pal_lut, img_data, pitch, dst_pos, src_pos)
|
|
return master_tilesheets
|
|
|
|
#if __name__ == "__main__":
|
|
# ROM = util.load_rom(sys.argv[1] if len(sys.argv) >= 2 else None, True)
|
|
# decode_font()
|
|
|
|
|
|
|