metadata
This commit is contained in:
parent
fafdbb9e54
commit
b20c96e366
|
@ -0,0 +1,112 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# extract metadata from uxn rom files.
|
||||
#
|
||||
# see https://wiki.xxiivv.com/site/manifest.html
|
||||
#
|
||||
# (also converts the icon to a monochrome BMP file.)
|
||||
|
||||
from sys import argv, exit
|
||||
|
||||
# convert two bytes into an address, subtracting for zero page
|
||||
def toaddr(x, y):
|
||||
return x * 256 + y - 256
|
||||
|
||||
# parse null-terminated string
|
||||
def parsestr(data, start):
|
||||
end = start + data[start:].find(b'\0')
|
||||
return end + 1, data[start:end].decode('ascii')
|
||||
|
||||
# convert 64x64 icn to bmp
|
||||
# since we are always dealing with a 1-bit 64x64
|
||||
# image we can just hardcode the BMP header data
|
||||
def tobmp(icn):
|
||||
bmp = b"BM" # 0: bitmap file
|
||||
bmp += b"\x20\x10\x00\x00" # 2: file size: 4128 (32 + 4096)
|
||||
bmp += b"\x00\x00" # 6: reserved: 0
|
||||
bmp += b"\x00\x00" # 8: reserved: 0
|
||||
bmp += b"\x20\x00\x00\x00" # 10: pixel data offset: 32
|
||||
bmp += b"\x0c\x00\x00\x00" # 14: header size: 12
|
||||
bmp += b"\x40\x00" # 18: width in pixels: 64
|
||||
bmp += b"\x40\x00" # 20: height in pixels: 64
|
||||
bmp += b"\x01\x00" # 22: color planes: 1
|
||||
bmp += b"\x01\x00" # 24: bits per pixel: 1
|
||||
bmp += b"\xff\xff\xff" # 26: color 0
|
||||
bmp += b"\x00\x00\x00" # 29: color 1
|
||||
# 32: start of pixel data
|
||||
|
||||
# ICN is composed of a grid of 8x8 tiles. for BMP we need
|
||||
# to create 64 rows of 64 pixels each.
|
||||
rows = []
|
||||
for offset in range(0, 512, 64):
|
||||
for y in range(0, 8):
|
||||
rows.append([])
|
||||
for x in range(0, 64, 8):
|
||||
rows[-1].append(icn[offset + y + x])
|
||||
for row in reversed(rows):
|
||||
bmp += bytes(row)
|
||||
return bmp
|
||||
|
||||
# read the icon from the given binary data and address
|
||||
def parseicon(path, data, pos0):
|
||||
if len(data[pos0:]) < 512:
|
||||
print('needed 512 bytes, only had %d' % len(data[pos0:]))
|
||||
icn = data[pos0:pos0+512]
|
||||
bmp = tobmp(icn)
|
||||
return bmp
|
||||
|
||||
# parse the metadata from the given binary data and address
|
||||
def parsemeta(path, data, pos0):
|
||||
a,b,c,d = data[pos0:pos0+4]
|
||||
endaddr = toaddr(a, b)
|
||||
iconaddr = toaddr(c, d)
|
||||
pos1, name = parsestr(data, pos0 + 4)
|
||||
pos2, version = parsestr(data, pos1)
|
||||
pos3, desc = parsestr(data, pos2)
|
||||
pos4, author = parsestr(data, pos3)
|
||||
if pos4 != endaddr:
|
||||
print('%s: data ended by %04x but expected %04x' % (path, pos4, endaddr))
|
||||
bmp = parseicon(path, data, iconaddr)
|
||||
return {'name': name, 'version': version, 'desc': desc, 'author': author, 'icon': bmp}
|
||||
|
||||
# parse metadata from the given uxn rom
|
||||
def parse(path):
|
||||
f = open(path, 'rb')
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
if len(data) < 6:
|
||||
print("%s: file too small (%d bytes)" % (path, len(data)))
|
||||
exit(1)
|
||||
|
||||
a,x,y,d,e,f = data[:6]
|
||||
if a != 0xa0 or d != 0x80 or e != 0xf0 or f != 0x37:
|
||||
print('%s: no metadata detected' % path)
|
||||
exit(1)
|
||||
|
||||
return parsemeta(path, data, toaddr(x, y))
|
||||
|
||||
# go for it!
|
||||
def main():
|
||||
if len(argv[1:]) != 1:
|
||||
print('usage: %s ROMFILE' % argv[0])
|
||||
exit(1)
|
||||
|
||||
path = argv[1]
|
||||
meta = parse(path)
|
||||
|
||||
iconpath = path + '.bmp'
|
||||
with open(iconpath, 'wb') as f:
|
||||
f.write(meta['icon'])
|
||||
|
||||
print('%s:' % path)
|
||||
print(' Name: %s' % meta['name'])
|
||||
print(' Version: %s' % meta['version'])
|
||||
print(' Description: %s' % meta['desc'])
|
||||
print(' Author: %s' % meta['author'])
|
||||
print('')
|
||||
print(' Icon: %s' % iconpath)
|
||||
print('')
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue