alpine: Select ground sprite based on terrain (GRF logic generator)

This commit is contained in:
Pavel Stupnikov
2021-12-15 20:05:52 +03:00
parent 3b650fbdbf
commit 954a3cba15
5 changed files with 668 additions and 124 deletions

View File

@@ -114,7 +114,16 @@ replace_coastal_sprites('gfx/water/seashore_grid_temperate.gimp.png', 1, 1)
png = grf.ImageFile("gfx/meadow_grid_temperate.png") png = grf.ImageFile("gfx/meadow_grid_temperate.png")
gen.add_sprite(grf.SpriteSet(grf.OBJECT, 19)) gen.add_sprite(grf.SpriteSet(grf.OBJECT, 19))
tmpl_ground_sprites(lambda *args, **kw: gen.add_sprite(grf.FileSprite(png, *args, **kw)), 1, 1) tmpl_ground_sprites(lambda *args, **kw: gen.add_sprite(grf.FileSprite(png, *args, **kw)), 1, 1)
gen.add_sprite(grf.BasicSpriteLayout(grf.OBJECT, 255)) # gen.add_sprite(grf.BasicSpriteLayout(grf.OBJECT, 255))
gen.add_sprite(grf.AdvancedSpriteLayout(
grf.OBJECT, 255,
ground={
'sprite': 0,
'pal': 32768,
'flags': 2,
'add': grf.Temp(0),
}
))
gen.add_sprite(grf.Object(0, gen.add_sprite(grf.Object(0,
label=b'FLMA', label=b'FLMA',
@@ -124,15 +133,29 @@ gen.add_sprite(grf.Object(0,
flags=grf.Object.Flags.HAS_NO_FOUNDATION | grf.Object.Flags.ALLOW_UNDER_BRIDGE, flags=grf.Object.Flags.HAS_NO_FOUNDATION | grf.Object.Flags.ALLOW_UNDER_BRIDGE,
)) ))
gen.add_sprite(grf.VarAction2(
feature=grf.OBJECT,
use_related=False,
set_id=0,
ranges={0: 0, 1: 1, 2: 2, 4: 4, 8: 8, 9: 9, 3: 3, 6: 6, 12: 12, 5: 5, 10: 10, 11: 11, 7: 7, 14: 14, 13: 13, 27: 17, 23: 16, 30: 18, 29: 15},
default=0,
code='tile_slope'
))
gen.add_sprite(grf.VarAction2(
feature=grf.OBJECT,
use_related=False,
set_id=255,
ranges={0: grf.Set(255)},
default=grf.Set(255),
code='TEMP[0] = call(0)'
))
gen.add_sprite(grf.Action3(grf.OBJECT, [0], [[255, 255]], 255)) gen.add_sprite(grf.Action3(grf.OBJECT, [0], [[255, 255]], 255))
{0: 0, 1: 1, 2: 2, 4: 4, 8: 8, 9: 9, 3: 3, 6: 6, 12: 12, 5: 5, 10: 10, 11: 11, 7: 7, 14: 14, 13: 13, 27: 17, 23: 16, 30: 18, 29: 15}
# CREEKS # CREEKS
# MEADOW # MEADOW
# spriteset (meadow_groundsprites, "gfx/meadow_grid_temperate.png") { tmpl_groundsprites(1, 1) } # spriteset (meadow_groundsprites, "gfx/meadow_grid_temperate.png") { tmpl_groundsprites(1, 1) }

View File

@@ -6,6 +6,9 @@ import spectra
import struct import struct
import numpy as np import numpy as np
from parser import Node, Expr, Value, Var, Temp, Perm, Call, parse_code, OP_INIT
to_spectra = lambda r, g, b: spectra.rgb(float(r) / 255., float(g) / 255., float(b) / 255.) to_spectra = lambda r, g, b: spectra.rgb(float(r) / 255., float(g) / 255., float(b) / 255.)
# working with DOS palette only # working with DOS palette only
PALETTE = (0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 255, 255, 255) PALETTE = (0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 255, 255, 255)
@@ -21,6 +24,12 @@ BPP_8, BPP_32 = range(2)
OBJECT = 0x0f OBJECT = 0x0f
def hex_str(s):
if isinstance(s, (bytes, memoryview)):
return ':'.join('{:02x}'.format(b) for b in s)
return ':'.join('{:02x}'.format(ord(c)) for c in s)
def color_distance(c1, c2): def color_distance(c1, c2):
rmean = (c1.rgb[0] + c2.rgb[0]) / 2. rmean = (c1.rgb[0] + c2.rgb[0]) / 2.
r = c1.rgb[0] - c2.rgb[0] r = c1.rgb[0] - c2.rgb[0]
@@ -46,82 +55,6 @@ def find_best_color(x, in_range=SAFE_COLORS):
# assert im.mode == 'RGB', im.mode # assert im.mode == 'RGB', im.mode
# data = np.array(im) # data = np.array(im)
OP_ADD = 0x00
OP_SUB = 0x01
OP_TSTO = 0x0E
OP_PSTO = 0x10
OPERATORS = {
OP_ADD: ('{a} + {b}', 2),
OP_SUB: ('{a} - {b}', 2),
OP_TSTO: ('TEMP[{b}] = {a}', 1),
OP_PSTO: ('PERM[{b}] = {a}', 1),
}
DEFAULT_INDENT_STR = ' '
class Node:
def __init__(self):
pass
def _make_node(self, value):
if isinstance(value, int):
return Value(value)
assert isinstance(value, Node)
return value
def __add__(self, other):
return Expr(OP_ADD, self, self._make_node(other))
def __sub__(self, other):
return Expr(OP_SUB, self, self._make_node(other))
def store_temp(self, register):
return Expr(OP_TSTO, self, self._make_node(register))
def store_perm(self, register):
return Expr(OP_PSTO, self, self._make_node(register))
def format(self, parent_priority=0, indent=0, indent_str=DEFAULT_INDENT_STR):
raise NotImplementedError
def __str__(self):
return self.format()
def __repr__(self):
return self.format()
class Expr(Node):
def __init__(self, op, a, b):
self.op = op
self.a = a
self.b = b
def format(self, parent_priority=0, indent=0, indent_str=DEFAULT_INDENT_STR):
if self.op in OPERATORS:
fmt, prio = OPERATORS[self.op]
else:
fmt, prio = '{a} <{self.op:02x}> {b}', 1
astr = self.a.format(prio, indent=0)
bstr = self.b.format(prio, indent=0)
res = fmt.format(a=astr, b=bstr)
suffix = indent_str * indent
if prio <= parent_priority:
return suffix + f'({res})'
return suffix + res
class Value(Node):
def __init__(self, value):
super().__init__()
self.value = value
def format(self, parent_priority=0, indent=0, indent_str=DEFAULT_INDENT_STR):
return indent_str * indent + str(self.value)
class BaseSprite: class BaseSprite:
def get_data(self): def get_data(self):
raise NotImplemented raise NotImplemented
@@ -476,11 +409,11 @@ class SpriteSet(Action1):
class BasicSpriteLayout(LazyBaseSprite): class BasicSpriteLayout(LazyBaseSprite):
def __init__(self, feature, ref_id, xofs=0, yofs=0, extent=(0, 0, 0)): def __init__(self, feature, set_id, xofs=0, yofs=0, extent=(0, 0, 0)):
super().__init__() super().__init__()
assert feature in (0x07, 0x09, OBJECT, 0x11), feature assert feature in (0x07, 0x09, OBJECT, 0x11), feature
self.feature = feature self.feature = feature
self.ref_id = ref_id self.set_id = set_id
self.xofs = xofs self.xofs = xofs
self.yofs = yofs self.yofs = yofs
self.extent = extent self.extent = extent
@@ -489,13 +422,135 @@ class BasicSpriteLayout(LazyBaseSprite):
return id | (mode << 14) | (recolor << 16) | (draw_in_transparent << 30) | (use_last << 31) return id | (mode << 14) | (recolor << 16) | (draw_in_transparent << 30) | (use_last << 31)
def _encode(self): def _encode(self):
return struct.pack('<BBBBIIbbBBB', 0x02, self.feature, self.ref_id, 0, return struct.pack('<BBBBIIbbBBB', 0x02, self.feature, self.set_id, 0,
# self._encode_sprite(), self._encode_sprite(), # self._encode_sprite(), self._encode_sprite(),
self._encode_sprite(use_last=True), 0, self._encode_sprite(use_last=True), 0,
self.xofs, self.yofs, self.xofs, self.yofs,
*self.extent) *self.extent)
class AdvancedSpriteLayout(LazyBaseSprite):
def __init__(self, feature, set_id, ground, sprites=()):
super().__init__()
assert feature in (0x07, 0x09, OBJECT, 0x11), feature
assert len(sprites) < 64, len(sprites)
self.feature = feature
self.set_id = set_id
self.ground = ground
self.sprites = sprites
def _encode_sprite(self, sprite, aux=False):
res = struct.pack('<HHH', sprite['sprite'], sprite['pal'], sprite['flags'])
if aux:
delta = s.get('delta', (0, 0, 0))
is_parent = bool(sprite.get('parent'))
if not is_parent:
delta = (delta[0], delta[1], 0x80)
res += struct.pack('<BBB', *delta)
if is_parent:
res += struct.pack('<BBB', *sprite.get('size'))
for k in ('dodraw', 'add', 'palette', 'sprite_var10', 'palette_var10'):
if k in sprite:
val = sprite[k]
if k == 'add':
assert isinstance(val, Temp)
val = val.register
res += bytes((val, ))
# TODO deltas
return res
def _encode(self):
res = struct.pack('<BBBB', 0x02, self.feature, self.set_id, len(self.sprites) + 0x40)
res += self._encode_sprite(self.ground)
for s in self.sprites:
res += self._encode_sprite(s, True)
return res
class Set:
def __init__(self, set_id):
self.value = set_id
self.is_callback = bool(set_id & 0x8000)
self.set_id = set_id & 0x7fff
def __str__(self):
if self.is_callback:
return f'CB({self.set_id})'
return f'Set({self.set_id})'
__repr__ = __str__
class CB(Set):
def __init__(self, value):
super().__init__(value | 0x8000)
class Range:
def __init__(self, low, high, set):
self.set = set
self.low = low
self.high = high
def __str__(self):
if self.low == self.high:
return f'{self.low} -> {self.set}'
return f'{self.low}..{self.high} -> {self.set}'
__repr__ = __str__
class VarAction2(LazyBaseSprite):
def __init__(self, feature, set_id, use_related, ranges, default, code):
super().__init__()
self.feature = feature
assert feature == OBJECT, feature
self.set_id = set_id
self.use_related = use_related
self.ranges = ranges
self.default = default
self.code = parse_code(code)
def _get_set_value(self, set_obj):
if isinstance(set_obj, Set):
return set_obj.value
assert isinstance(set_obj, int)
return set_obj | 0x8000
def _encode(self):
res = bytes((0x02, self.feature, self.set_id, 0x8a if self.use_related else 0x89))
code = self.code[0].compile(register=0x80)[1]
for c in self.code[1:]:
code += bytes((OP_INIT,))
code += c.compile(register=0x80)[1]
# print('CODE', hex_str(code))
res += code[:-5]
res += bytes((code[-5] & ~0x20,)) # mark the end of a chain
res += code[-4:]
res += bytes((len(self.ranges),))
# print('RES', hex_str(res))
ranges = self.ranges
if isinstance(ranges, dict):
ranges = self.ranges.items()
for r in ranges:
if isinstance(r, Range):
set_obj = r.set
low = r.low
high = r.high
else:
set_obj = r[1]
low = r[0]
high = r[0]
# TODO split (or validate) negative-positive ranges
res += struct.pack('<HII', self._get_set_value(set_obj), low, high)
res += struct.pack('<H', self._get_set_value(self.default))
return res
class Action3(LazyBaseSprite): class Action3(LazyBaseSprite):
def __init__(self, feature, ids, maps, default): def __init__(self, feature, ids, maps, default):
super().__init__() super().__init__()

437
grf/alpine/parser.py Normal file
View File

@@ -0,0 +1,437 @@
import struct
import ply.lex
import ply.yacc
OP_ADD = 0x00
OP_SUB = 0x01
OP_MIN = 0x02
OP_MAX = 0x03
OP_MINU = 0x04
OP_MAXU = 0x05
OP_MUL = 0x0A
OP_BINAND = 0x0B
OP_BINOR = 0x0C
OP_BINXOR = 0x0D
OP_TSTO = 0x0E
OP_INIT = 0x0F
OP_PSTO = 0x10
OP_ROT = 0x11
OP_CMP = 0x12
OP_CMPU = 0x13
OP_SHL = 0x14
OP_SHRU = 0x15
OP_SHR = 0x16
OPERATORS = {
OP_ADD: ('{a} + {b}', 5, False),
OP_SUB: ('{a} - {b}', 5, True),
OP_MIN: ('min({a}, {b})', 7, False),
OP_MAX: ('max({a}, {b})', 7, False),
OP_MINU: ('minu({a}, {b})', 7, False),
OP_MAXU: ('maxu({a}, {b})', 7, False),
OP_MUL: ('{a} * {b}', 6, False),
OP_BINAND: ('{a} & {b}', 4, True),
OP_BINOR: ('{a} | {b}', 4, True),
OP_BINXOR: ('{a} ^ {b}', 4, True),
OP_TSTO: ('TEMP[{b}] = {a}', 2, True),
OP_INIT: (None, 1, False),
OP_PSTO: ('PERM[{b}] = {a}', 2, True),
OP_ROT: ('rot({a}, {b})', 7, False),
OP_CMP: ('cmp({a}, {b})', 7, False),
OP_CMPU: ('cmpu({a}, {b})', 7, False),
OP_SHL: ('{a} << {b}', 4, True),
OP_SHRU: ('{a} u>> {b}', 4, True),
OP_SHR: ('{a} >> {b}', 4, True),
}
DEFAULT_INDENT_STR = ' '
class Node:
def __init__(self):
pass
def _make_node(self, value):
if isinstance(value, int):
return Value(value)
assert isinstance(value, Node)
return value
def __add__(self, other):
return Expr(OP_ADD, self, self._make_node(other))
def __sub__(self, other):
return Expr(OP_SUB, self, self._make_node(other))
def store_temp(self, register):
return Expr(OP_TSTO, self, self._make_node(register))
def store_perm(self, register):
return Expr(OP_PSTO, self, self._make_node(register))
def format(self, parent_priority=0):
raise NotImplementedError
def __str__(self):
return '\n'.join(self.format())
def __repr__(self):
return '; '.join(self.format())
def compile(self, register, shift=0, and_mask=0xffffffff):
raise NotImplementedError
class Expr(Node):
def __init__(self, op, a, b):
self.op = op
self.a = a
self.b = b
def format(self, parent_priority=0):
if self.op in OPERATORS:
fmt, prio, bracket = OPERATORS[self.op]
else:
fmt, prio, bracket = f'{{a}} <{self.op:02x}> {{b}}', 1, True
ares = self.a.format(prio - 1)
bres = self.b.format(prio - int(not bracket))
assert len(bres) == 1, bres
if self.op == OP_INIT:
ares.append(bres[0])
return ares
res = fmt.format(a=ares[-1], b=bres[-1])
if prio <= parent_priority:
res = f'({res})'
ares[-1] = res
return ares
def compile(self, register, shift=0, and_mask=0xffffffff):
is_value, b_code = self.b.compile(register, shift, and_mask)
if is_value:
# Second arg is a simple value, do operation directly
res = self.a.compile(register, shift, and_mask)[1]
res += bytes((self.op,))
res += b_code
return False, res
# Calculate secord arg first and store in in a temp var
res += b_code
res += struct.pack('<BBBIB', OP_TSTO, 0x1a, 0x20, register, OP_INIT)
res += self.a.compile(register + 1, shift, and_mask)[1]
res += struct.pack('<BBBI', self.op, 0x7d, 0x20, 0xffffffff)
return False, res
class Value(Node):
def __init__(self, value):
super().__init__()
self.value = value
def format(self, parent_priority=0):
return [str(self.value)]
def compile(self, register, shift=0, and_mask=0xffffffff):
assert shift < 0x20, shift
assert and_mask <= 0xffffffff, and_mask
valueadj = (self.value >> shift) & and_mask
return True, struct.pack('<BBI', 0x1a, 0x20, valueadj)
class Var(Node):
def __init__(self, name):
super().__init__()
self.name = name
def format(self, parent_priority=0):
return [self.name]
def compile(self, register, shift=0, and_mask=0xffffffff):
var_data = VARS = {
'tile_slope': (0x41, 8, 0x1f)
}.get(self.name)
if var_data is None:
raise ValueError(f'Unknown variable `{self.name}`')
and_mask &= var_data[2] >> shift
shift += var_data[1]
assert shift < 0x20, shift
assert and_mask <= 0xffffffff, and_mask
return True, struct.pack('<BBI', var_data[0], 0x20 | shift, and_mask)
class Temp(Node):
def __init__(self, register):
super().__init__()
self.register = register
def format(self, parent_priority=0):
return [f'TEMP[{self.register}]']
def compile(self, register, shift=0, and_mask=0xffffffff):
assert shift < 0x20, shift
assert and_mask <= 0xffffffff, and_mask
return True, struct.pack('<BBI', 0x7d, 0x20 | shift, and_mask)
class Perm(Node):
def __init__(self, register):
super().__init__()
self.register = register
def format(self, parent_priority=0):
return [f'PERM[{self.register}]']
def compile(self, register, shift=0, and_mask=0xffffffff):
assert shift < 0x20, shift
assert and_mask <= 0xffffffff, and_mask
return True, struct.pack('<BBI', 0x7c, 0x20 | shift, and_mask)
class Call(Node):
def __init__(self, subroutine):
assert 0 <= subroutine < 256, subroutine
self.subroutine = subroutine
def format(self, parent_priority=0):
return [f'call({self.subroutine})']
def compile(self, register, shift=0, and_mask=0xffffffff):
assert shift < 0x20, shift
assert and_mask <= 0xffffffff, and_mask
return True, struct.pack('<BBBI', 0x7e, self.subroutine, 0x20 | shift, and_mask)
tokens = (
'NAME', 'NUMBER', 'NEWLINE',
'ADD', 'SUB', 'MUL',
'BINAND', 'BINOR', 'BINXOR',
'ASSIGN', 'COMMA',
'LPAREN', 'RPAREN', 'LBRACKET', 'RBRACKET',
)
# Tokens
t_ADD = r'\+'
t_SUB = r'-'
t_MUL = r'\*'
# t_DIV = r'/'
# t_MOD = r'%'
t_BINAND = r'\&'
t_BINOR = r'\|'
t_BINXOR = r'\^'
t_ASSIGN = r'='
t_COMMA = r','
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LBRACKET = r'\['
t_RBRACKET = r'\]'
t_NAME = r'[a-zA-Z_][a-zA-Z0-9_]*'
def t_NUMBER(t):
r'\d+'
try:
t.value = int(t.value)
except ValueError:
print("Integer value too large %d", t.value)
t.value = 0
return t
# Ignored characters
t_ignore = ' \t'
def t_NEWLINE(t):
r'\n+'
t.lexer.lineno += t.value.count("\n")
return t
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
# Parsing rules
precedence = (
('left', 'BINOR'),
('left', 'BINXOR'),
('left', 'BINAND'),
('left', 'ADD', 'SUB'),
('left', 'MUL'),
# ('right', 'UMINUS'),
)
def p_lines(t):
'''lines : line
| expression
| lines line
'''
# print('LINES', t[1])
if len(t) == 2:
lines, line = [], t[1]
else:
lines, line = t[1], t[2]
if line is not None:
lines.append(line)
t[0] = lines
def p_line(t):
'''line : expression NEWLINE
| NEWLINE
'''
# print('LINE', t[1], len(t))
if len(t) == 2:
t[0] = None
else:
t[0] = t[1]
# def p_statement_expr(t):
# 'statement : expression'
# t[0] = t[1]
# def p_storagge(t):
# 'storage : NAME LBRACKET NUMBER RBRACKET'
# storage = {
# 'TEMP': grf.Temp,
# 'PERM': grf.Perm,
# }.get(t[1])
# assert storage is not None, t[1]
# register = int(t[3])
# t[0] = storage(grf.Value(register))
def p_expression_binop(t):
'''expression : expression ADD expression
| expression SUB expression
| expression MUL expression
| expression BINAND expression
| expression BINOR expression
| expression BINXOR expression
'''
op = {
'+': OP_ADD,
'-': OP_SUB,
'*': OP_MUL,
'&': OP_BINAND,
'|': OP_BINOR,
'^': OP_BINXOR,
}.get(t[2])
assert op is not None, t[2]
# print(op, t[1], t[3])
t[0] = Expr(op, t[1], t[3])
# def p_storagge(t):
# 'storage : NAME LBRACKET NUMBER RBRACKET'
# storage = {
# 'TEMP': grf.Temp,
# 'PERM': grf.Perm,
# }.get(t[1])
# assert storage is not None, t[1]
# register = int(t[3])
# t[0] = storage(grf.Value(register))
def p_expression_assign(t):
'expression : NAME LBRACKET NUMBER RBRACKET ASSIGN expression'
assert t[1] in ('TEMP', 'PERM'), t[1]
op = OP_TSTO if t[1] == 'TEMP' else OP_PSTO
register = int(t[3])
t[0] = Expr(op, t[6], Value(register))
# def p_expression_uminus(t):
# 'expression : SUB expression %prec UMINUS'
# t[0] = -t[2]
def p_expression_call1(t):
'expression : NAME LPAREN NUMBER RPAREN'
assert t[1] == 'call', t[1]
t[0] = Call(int(t[3]))
def p_expression_call2(t):
'expression : NAME LPAREN expression COMMA expression RPAREN'
op = {
'min': OP_MIN,
'max': OP_MAX,
'minu': OP_MINU,
'maxu': OP_MAXU,
'rot': OP_ROT,
'cmp': OP_CMP,
'cmpu': OP_CMPU,
}.get(t[1])
assert op is not None, t[1]
t[0] = Expr(op, t[3], t[5])
def p_expression_group(t):
'expression : LPAREN expression RPAREN'
t[0] = t[2]
def p_expression_storage(t):
'expression : NAME LBRACKET NUMBER RBRACKET'
assert t[1] in ('TEMP', 'PERM'), t[1]
cls = Temp if t[1] == 'TEMP' else Perm
register = int(t[3])
t[0] = cls(Value(register))
def p_expression_number(t):
'expression : NUMBER'
t[0] = Value(int(t[1]))
def p_expression_name(t):
'expression : NAME'
t[0] = Var(t[1])
def p_error(t):
# stack_state_str = ' '.join([symbol.type for symbol in parser.symstack][1:])
# print('Syntax error in input! Parser State:{} {} . {}'
# .format(parser.state,
# stack_state_str,
# t))
if t is None:
print("Unexpected syntax error")
return
print(f'Syntax error at `{t.value}` line {t.lineno}')
def parse_code(code):
lexer = ply.lex.lex()
parser = ply.yacc.yacc()
return parser.parse(code)
if __name__ == "__main__":
res = parse_code('''
TEMP[128] = (cmp(tile_slope, 30) & 1) * 18
TEMP[129] = (cmp(tile_slope, 29) & 1) * 15
TEMP[130] = (cmp(tile_slope, 27) & 1) * 17
TEMP[131] = (cmp(tile_slope, 23) & 1) * 16
TEMP[132] = min(cmp(tile_slope, 0), 1)
TEMP[134] = (min((cmp(tile_slope, 14) ^ 2), 1) & TEMP[132]) * tile_slope + TEMP[131] + TEMP[130] + TEMP[129] + TEMP[128]
''')
for line in res:
print(line.format())

48
grf/alpine/parsetab.py Normal file
View File

@@ -0,0 +1,48 @@
# parsetab.py
# This file is automatically generated. Do not edit.
# pylint: disable=W,C,R
_tabversion = '3.10'
_lr_method = 'LALR'
_lr_signature = 'leftBINORleftBINXORleftBINANDleftADDSUBleftMULADD ASSIGN BINAND BINOR BINXOR COMMA LBRACKET LPAREN MUL NAME NEWLINE NUMBER RBRACKET RPAREN SUBlines : line\n | expression\n | lines line\n line : expression NEWLINE\n | NEWLINE\n expression : expression ADD expression\n | expression SUB expression\n | expression MUL expression\n | expression BINAND expression\n | expression BINOR expression\n | expression BINXOR expression\n expression : NAME LBRACKET NUMBER RBRACKET ASSIGN expressionexpression : NAME LPAREN NUMBER RPARENexpression : NAME LPAREN expression COMMA expression RPARENexpression : LPAREN expression RPARENexpression : NAME LBRACKET NUMBER RBRACKETexpression : NUMBERexpression : NAME'
_lr_action_items = {'NEWLINE':([0,1,2,3,4,5,6,8,9,10,20,21,22,23,24,25,29,30,31,35,36,],[4,4,-1,10,-5,-18,-17,-3,10,-4,-6,-7,-8,-9,-10,-11,-15,-16,-13,-12,-14,]),'NAME':([0,1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,18,20,21,22,23,24,25,29,30,31,32,33,35,36,],[5,5,-1,-2,-5,-18,-17,5,-3,-4,5,5,5,5,5,5,5,-6,-7,-8,-9,-10,-11,-15,-16,-13,5,5,-12,-14,]),'LPAREN':([0,1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,18,20,21,22,23,24,25,29,30,31,32,33,35,36,],[7,7,-1,-2,-5,18,-17,7,-3,-4,7,7,7,7,7,7,7,-6,-7,-8,-9,-10,-11,-15,-16,-13,7,7,-12,-14,]),'NUMBER':([0,1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17,18,20,21,22,23,24,25,29,30,31,32,33,35,36,],[6,6,-1,-2,-5,-18,-17,6,-3,-4,6,6,6,6,6,6,26,27,-6,-7,-8,-9,-10,-11,-15,-16,-13,6,6,-12,-14,]),'$end':([1,2,3,4,5,6,8,10,20,21,22,23,24,25,29,30,31,35,36,],[0,-1,-2,-5,-18,-17,-3,-4,-6,-7,-8,-9,-10,-11,-15,-16,-13,-12,-14,]),'ADD':([3,5,6,9,19,20,21,22,23,24,25,27,28,29,30,31,34,35,36,],[11,-18,-17,11,11,-6,-7,-8,11,11,11,-17,11,-15,-16,-13,11,11,-14,]),'SUB':([3,5,6,9,19,20,21,22,23,24,25,27,28,29,30,31,34,35,36,],[12,-18,-17,12,12,-6,-7,-8,12,12,12,-17,12,-15,-16,-13,12,12,-14,]),'MUL':([3,5,6,9,19,20,21,22,23,24,25,27,28,29,30,31,34,35,36,],[13,-18,-17,13,13,13,13,-8,13,13,13,-17,13,-15,-16,-13,13,13,-14,]),'BINAND':([3,5,6,9,19,20,21,22,23,24,25,27,28,29,30,31,34,35,36,],[14,-18,-17,14,14,-6,-7,-8,-9,14,14,-17,14,-15,-16,-13,14,14,-14,]),'BINOR':([3,5,6,9,19,20,21,22,23,24,25,27,28,29,30,31,34,35,36,],[15,-18,-17,15,15,-6,-7,-8,-9,-10,-11,-17,15,-15,-16,-13,15,15,-14,]),'BINXOR':([3,5,6,9,19,20,21,22,23,24,25,27,28,29,30,31,34,35,36,],[16,-18,-17,16,16,-6,-7,-8,-9,16,-11,-17,16,-15,-16,-13,16,16,-14,]),'LBRACKET':([5,],[17,]),'RPAREN':([5,6,19,20,21,22,23,24,25,27,29,30,31,34,35,36,],[-18,-17,29,-6,-7,-8,-9,-10,-11,31,-15,-16,-13,36,-12,-14,]),'COMMA':([5,6,20,21,22,23,24,25,27,28,29,30,31,35,36,],[-18,-17,-6,-7,-8,-9,-10,-11,-17,32,-15,-16,-13,-12,-14,]),'RBRACKET':([26,],[30,]),'ASSIGN':([30,],[33,]),}
_lr_action = {}
for _k, _v in _lr_action_items.items():
for _x,_y in zip(_v[0],_v[1]):
if not _x in _lr_action: _lr_action[_x] = {}
_lr_action[_x][_k] = _y
del _lr_action_items
_lr_goto_items = {'lines':([0,],[1,]),'line':([0,1,],[2,8,]),'expression':([0,1,7,11,12,13,14,15,16,18,32,33,],[3,9,19,20,21,22,23,24,25,28,34,35,]),}
_lr_goto = {}
for _k, _v in _lr_goto_items.items():
for _x, _y in zip(_v[0], _v[1]):
if not _x in _lr_goto: _lr_goto[_x] = {}
_lr_goto[_x][_k] = _y
del _lr_goto_items
_lr_productions = [
("S' -> lines","S'",1,None,None,None),
('lines -> line','lines',1,'p_lines','parser.py',272),
('lines -> expression','lines',1,'p_lines','parser.py',273),
('lines -> lines line','lines',2,'p_lines','parser.py',274),
('line -> expression NEWLINE','line',2,'p_line','parser.py',288),
('line -> NEWLINE','line',1,'p_line','parser.py',289),
('expression -> expression ADD expression','expression',3,'p_expression_binop','parser.py',316),
('expression -> expression SUB expression','expression',3,'p_expression_binop','parser.py',317),
('expression -> expression MUL expression','expression',3,'p_expression_binop','parser.py',318),
('expression -> expression BINAND expression','expression',3,'p_expression_binop','parser.py',319),
('expression -> expression BINOR expression','expression',3,'p_expression_binop','parser.py',320),
('expression -> expression BINXOR expression','expression',3,'p_expression_binop','parser.py',321),
('expression -> NAME LBRACKET NUMBER RBRACKET ASSIGN expression','expression',6,'p_expression_assign','parser.py',350),
('expression -> NAME LPAREN NUMBER RPAREN','expression',4,'p_expression_call1','parser.py',362),
('expression -> NAME LPAREN expression COMMA expression RPAREN','expression',6,'p_expression_call2','parser.py',368),
('expression -> LPAREN expression RPAREN','expression',3,'p_expression_group','parser.py',383),
('expression -> NAME LBRACKET NUMBER RBRACKET','expression',4,'p_expression_storage','parser.py',388),
('expression -> NUMBER','expression',1,'p_expression_number','parser.py',396),
('expression -> NAME','expression',1,'p_expression_name','parser.py',401),
]

View File

@@ -2,7 +2,7 @@ import sys
import struct import struct
from nml import lz77 from nml import lz77
from grf import Node, Expr, Value from grf import Node, Expr, Value, Var, Temp, Perm, Range, Set, Call
def hex_str(s): def hex_str(s):
@@ -270,33 +270,6 @@ def read_sprite_layout_registers(d, flags, is_parent):
return regs return regs
class Set:
def __init__(self, set_id):
self.is_callback = bool(set_id & 0x8000)
self.set_id = set_id & 0x7fff
def __str__(self):
if self.is_callback:
return f'CB({self.set_id})'
return f'Set({self.set_id})'
__repr__ = __str__
class Range:
def __init__(self, low, high, set):
self.set = set
self.low = low
self.high = high
def __str__(self):
if self.low == self.high:
return f'{self.low} -> {self.set}'
return f'{self.low}..{self.high} -> {self.set}'
__repr__ = __str__
def read_sprite_layout(d, num, no_z_position): def read_sprite_layout(d, num, no_z_position):
has_z_position = not no_z_position has_z_position = not no_z_position
has_flags = bool((num >> 6) & 1) has_flags = bool((num >> 6) & 1)
@@ -427,11 +400,6 @@ def get_va2_var(var):
return V2_OBJECT_VARS[var] return V2_OBJECT_VARS[var]
class Call(Node):
def __init__(self, subroutine):
self.suroutine = subroutine
class Generic(Node): class Generic(Node):
def __init__(self, var, shift, and_mask, type, add_val, divmod_val): def __init__(self, var, shift, and_mask, type, add_val, divmod_val):
self.var = var self.var = var
@@ -441,13 +409,13 @@ class Generic(Node):
self.add_val = add_val self.add_val = add_val
self.divmod_val = divmod_val self.divmod_val = divmod_val
def format(self, parent_priority=0, indent=0, indent_str=' '): def format(self, parent_priority=0):
addstr = '' addstr = ''
if self.type == 1: if self.type == 1:
addstr = ' +{self.add_val} /{self.divmod_val}' addstr = f' +{self.add_val} /{self.divmod_val}'
elif self.type == 2: elif self.type == 2:
addstr = ' +{self.add_val} %{self.divmod_val}' addstr = f' +{self.add_val} %{self.divmod_val}'
return (indent_str * indent) + f'(var: {self.var} >>{self.shift} &{self.and_mask}{addstr})' return [f'(var{self.var:02x} >>{self.shift} &{self.and_mask:x}{addstr})']
def decode_action2(data): def decode_action2(data):
@@ -466,7 +434,7 @@ def decode_action2(data):
print(f'BASIC ground_sprite:{ground_sprite} building_sprite:{building_sprite} ' print(f'BASIC ground_sprite:{ground_sprite} building_sprite:{building_sprite} '
f'xofs:{xofs} yofs:{yofs} extent:({xext}, {yext}, {zext})') f'xofs:{xofs} yofs:{yofs} extent:({xext}, {yext}, {zext})')
return return
if num_ent1 < 0x3f: if num_ent1 <= 0x3f:
raise NotImplemented raise NotImplemented
if num_ent1 in (0x81, 0x82, 0x85, 0x86, 0x89, 0x8a): if num_ent1 in (0x81, 0x82, 0x85, 0x86, 0x89, 0x8a):
@@ -486,6 +454,7 @@ def decode_action2(data):
has_more = bool(varadj & 0x20) has_more = bool(varadj & 0x20)
node_type = varadj >> 6 node_type = varadj >> 6
and_mask = d.get_var(group_size) and_mask = d.get_var(group_size)
# print(f'CODE op:{op:x} var{var:02x} >>{shift} &{and_mask:x} has_more:{has_more} node_type:{node_type}')
if node_type != 0: if node_type != 0:
# old magic, use advaction2 instead # old magic, use advaction2 instead
add_val = d.get_var(group_size) add_val = d.get_var(group_size)
@@ -493,6 +462,14 @@ def decode_action2(data):
node = Generic(var, shift, and_mask, node_type, add_val, divmod_val) node = Generic(var, shift, and_mask, node_type, add_val, divmod_val)
elif var == 0x1a and shift == 0: elif var == 0x1a and shift == 0:
node = Value(and_mask) node = Value(and_mask)
elif (var, shift, and_mask) == (0x7c, 0, 0xffffffff):
node = Perm(param)
elif (var, shift, and_mask) == (0x7d, 0, 0xffffffff):
node = Temp(param)
elif (var, shift, and_mask) == (0x7e, 0, 0xffffffff):
node = Call(param)
elif (var, shift, and_mask) == (0x41, 8, 0x1f):
node = Var('tile_slope')
else: else:
node = Generic(var, shift, and_mask, 0, None, None) node = Generic(var, shift, and_mask, 0, None, None)
@@ -533,8 +510,8 @@ def decode_action2(data):
# if a['type'] != 0: # if a['type'] != 0:
# type_str = '+{add_val} /%{divmod_val}'.format(**a) # type_str = '+{add_val} /%{divmod_val}'.format(**a)
# print(f' op<{a["op"]}>:{op} var<{var:02x}>:{name}({fmt}){param_str} type:{a["type"]} >>{a["shift_num"]} &{a["and_mask"]:x}{type_str}') # print(f' op<{a["op"]}>:{op} var<{var:02x}>:{name}({fmt}){param_str} type:{a["type"]} >>{a["shift_num"]} &{a["and_mask"]:x}{type_str}')
print() for line in root.format():
print(root.format(indent=1)) print(' ', line)
return return
layout = read_sprite_layout(d, max(num_ent1, 1), num_ent1 == 0) layout = read_sprite_layout(d, max(num_ent1, 1), num_ent1 == 0)
@@ -684,6 +661,7 @@ ACTIONS = {
0x14: decode_action14, 0x14: decode_action14,
} }
def read_pseudo_sprite(f, nfo_line): def read_pseudo_sprite(f, nfo_line):
l = struct.unpack('<I', f.read(4))[0] l = struct.unpack('<I', f.read(4))[0]
if l == 0: if l == 0:
@@ -699,6 +677,7 @@ def read_pseudo_sprite(f, nfo_line):
decoder(data[1:]) decoder(data[1:])
return True return True
def decode_sprite(f, num): def decode_sprite(f, num):
data = b'' data = b''
while num > 0: while num > 0:
@@ -720,6 +699,7 @@ def decode_sprite(f, num):
if num != 0: raise RuntimeError('Corrupt sprite') if num != 0: raise RuntimeError('Corrupt sprite')
return data return data
def read_real_sprite(f): def read_real_sprite(f):
sprite_id = struct.unpack('<I', f.read(4))[0] sprite_id = struct.unpack('<I', f.read(4))[0]
if sprite_id == 0: if sprite_id == 0:
@@ -742,6 +722,7 @@ def read_real_sprite(f):
f.seek(start_pos + num - 1, 0) f.seek(start_pos + num - 1, 0)
return True return True
with open(sys.argv[1], 'rb') as f: with open(sys.argv[1], 'rb') as f:
print('Header:', hex_str(f.read(10))) print('Header:', hex_str(f.read(10)))
data_offest, compression = struct.unpack('<IB', f.read(5)) data_offest, compression = struct.unpack('<IB', f.read(5))