mirror of
https://github.com/MiSTer-devel/MSX1_MiSTer.git
synced 2026-04-19 03:04:26 +00:00
401 lines
17 KiB
Python
401 lines
17 KiB
Python
import os
|
|
import hashlib
|
|
import xml.etree.ElementTree as ET
|
|
import base64
|
|
|
|
|
|
ROM_DIR = 'ROM'
|
|
XML_DIR_COMP = 'Computer'
|
|
XML_DIR_FW = 'Extension'
|
|
EXTENSIONS = ["NONE" , "ROM" , "RAM" , "FDC" ,
|
|
"FM_PAC" , "MEGA_FLASH_ROM", "GM2" , "EMPTY" ]
|
|
|
|
MEM_DEVICE = ["NONE" , "ROM" , "RAM" , "FDC" ]
|
|
|
|
DEVICE_TYPES = ["NONE" , "KANJI" , "OPL3" , "RESET_STATUS" ]
|
|
|
|
CONFIG_TYPES = ["NONE" , "FDC" , "SLOT_A" , "SLOT_B" ,
|
|
"SLOT_INTERNAL" , "KBD_LAYOUT" , "CONFIG" , "DEVICE" ]
|
|
MAPPER_TYPES = ["MAPPER_UNUSED" , "MAPPER_RAM" , "MAPPER_AUTO" , "MAPPER_NONE" ,
|
|
"MAPPER_ASCII8" , "MAPPER_ASCII16", "MAPPER_KONAMI", "MAPPER_KONAMI_SCC",
|
|
"MAPPER_KOEI" , "MAPPER_LINEAR" , "MAPPER_RTYPE" , "MAPPER_WIZARDY" ,
|
|
"MAPPER_FMPAC" , "MAPPER_OFFSET" , "MAPPER_MFRSD1", "MAPPER_MFRSD2" ,
|
|
"MAPPER_MFRSD3" , "MAPPER_GM2" , "MAPPER_HALNOTE" ]
|
|
MSX_TYPES = ["MSX1", "MSX2"]
|
|
|
|
BLOCK_TYPES = {"NONE" : {"MEMORY": "NONE", "DEVICE" : "NONE" , "MAPPER" : "MAPPER_UNUSED" , "CONFIG" : "NONE" , "SRAM": 0 },
|
|
"RAM" : {"MEMORY": "RAM" , "DEVICE" : "NONE" , "MAPPER" : "MAPPER_NONE" , "CONFIG" : "SLOT_INTERNAL", "SRAM": 0 },
|
|
"RAM MAPPER" : {"MEMORY": "RAM" , "DEVICE" : "NONE" , "MAPPER" : "MAPPER_RAM" , "CONFIG" : "SLOT_INTERNAL", "SRAM": 0 },
|
|
"ROM" : {"MEMORY": "ROM", "DEVICE" : "NONE" , "MAPPER" : "MAPPER_NONE" , "CONFIG" : "SLOT_INTERNAL", "SRAM": 0 },
|
|
"FDC" : {"MEMORY": "FDC" , "DEVICE" : "NONE" , "MAPPER" : "MAPPER_NONE" , "CONFIG" : "SLOT_INTERNAL", "SRAM": 0 },
|
|
"SLOT A" : {"MEMORY": "ROM" , "DEVICE" : "NONE" , "MAPPER" : "MAPPER_UNUSED" , "CONFIG" : "SLOT_A" , "SRAM": 0 },
|
|
"SLOT B" : {"MEMORY": "ROM" , "DEVICE" : "NONE" , "MAPPER" : "MAPPER_UNUSED" , "CONFIG" : "SLOT_B" , "SRAM": 0 },
|
|
"KBD LAYOUT" : {"MEMORY": "NONE", "DEVICE" : "NONE" , "MAPPER" : "MAPPER_UNUSED" , "CONFIG" : "KBD_LAYOUT" , "SRAM": 0 },
|
|
"ROM_MIRROR" : {"MEMORY": "NONE", "DEVICE" : "NONE" , "MAPPER" : "MAPPER_NONE" , "CONFIG" : "SLOT_INTERNAL", "SRAM": 0 },
|
|
"IO_MIRROR" : {"MEMORY": "NONE", "DEVICE" : "NONE" , "MAPPER" : "MAPPER_UNUSED" , "CONFIG" : "SLOT_INTERNAL", "SRAM": 0 },
|
|
"MIRROR" : {"MEMORY": "NONE", "DEVICE" : "NONE" , "MAPPER" : "MAPPER_NONE" , "CONFIG" : "SLOT_INTERNAL", "SRAM": 0 },
|
|
"HALNOTE" : {"MEMORY": "ROM" , "DEVICE" : "NONE" , "MAPPER" : "MAPPER_HALNOTE", "CONFIG" : "SLOT_INTERNAL", "SRAM": 16 },
|
|
"MSX-MUSIC" : {"MEMORY": "ROM" , "DEVICE" : "OPL3" , "MAPPER" : "MAPPER_NONE" , "CONFIG" : "SLOT_INTERNAL", "SRAM": 0 },
|
|
}
|
|
|
|
def file_hash(filename):
|
|
"""Return the SHA1 hash of the file at `filename`."""
|
|
with open(filename, 'rb') as f:
|
|
return hashlib.sha1(f.read()).hexdigest()
|
|
|
|
def get_msx_type_id(typ):
|
|
"""Return the index of the given MSX type."""
|
|
return MSX_TYPES.index(typ)
|
|
|
|
|
|
def create_MSX_block(primary, secondary, values):
|
|
slotSubslot = ((int(primary) & 3) << 2) | ((int(secondary) & 3))
|
|
head = bytearray()
|
|
head.extend('MSX'.encode('ascii'))
|
|
|
|
config = BLOCK_TYPES[values["type"]]
|
|
head.append(CONFIG_TYPES.index(config['CONFIG']) << 4 | slotSubslot)
|
|
|
|
mem_device = config['MEMORY']
|
|
if values["type"] in ["IO_MIRROR", "MIRROR"] :
|
|
mem_device = BLOCK_TYPES[values['ref']['type']]["MEMORY"]
|
|
head.append(MEM_DEVICE.index(mem_device))
|
|
if config['MEMORY'] == "NONE" :
|
|
head.append(0x00)
|
|
head.append(0x00)
|
|
else :
|
|
head.append((int(values['count']) >> 8) & 255)
|
|
head.append(int(values['count']) & 255)
|
|
|
|
device = config['DEVICE']
|
|
if device != "NONE" :
|
|
head.append(DEVICE_TYPES.index(device) - 1)
|
|
else :
|
|
head.append(0xFF)
|
|
head.append(MAPPER_TYPES.index(config['MAPPER']))
|
|
|
|
mode = 0x00
|
|
param = 0x00
|
|
|
|
if "start" in values :
|
|
block = values['start']
|
|
offset = 0
|
|
for i in range(0,4) if values['count'] > 4 else range(values['count']) :
|
|
if values["type"] in ["ROM_MIRROR", "MIRROR"] :
|
|
mode |= 1 << (2*block)
|
|
param |= ((values["ref"]["start"] + i) & 3) << (2*block)
|
|
elif values["type"] in ["IO_MIRROR"] :
|
|
mode |= 3 << (2*block)
|
|
elif config['MAPPER'] != "MAPPER_UNUSED" :
|
|
mode |= 2 << (2*block)
|
|
param |= offset << (2*block)
|
|
elif config['DEVICE'] != "NONE" :
|
|
mode |= 3 << (2*block)
|
|
block = (block + 1) & 3
|
|
offset = (offset + 1) & 3
|
|
head.append(mode)
|
|
head.append(param)
|
|
head.append(values["pattern"])
|
|
head.append(config['SRAM'])
|
|
|
|
for i in range(len(head),16) :
|
|
head.append(0)
|
|
return head
|
|
|
|
def create_MSX_config(config) :
|
|
|
|
head = bytearray()
|
|
head.extend('MSX'.encode('ascii'))
|
|
head.append(CONFIG_TYPES.index("CONFIG") << 4)
|
|
head.append(config)
|
|
for i in range(len(head),16) :
|
|
head.append(0)
|
|
return head
|
|
|
|
def create_MSX_device(typ, size) :
|
|
head = bytearray()
|
|
head.extend('MSX'.encode('ascii'))
|
|
head.append(CONFIG_TYPES.index("DEVICE") << 4)
|
|
head.append(0x00)
|
|
head.append((size >> 8) & 255)
|
|
head.append(size & 255)
|
|
head.append(DEVICE_TYPES.index(typ) - 1)
|
|
for i in range(len(head),16) :
|
|
head.append(0)
|
|
return head
|
|
|
|
def create_FW_block(type, size):
|
|
"""Create and return a bytearray representing a block."""
|
|
head = bytearray()
|
|
head.extend('MSX'.encode('ascii'))
|
|
head.append(0)
|
|
head.append(type)
|
|
head.append(size >> 8)
|
|
head.append(size & 255)
|
|
for i in range(len(head),16) :
|
|
head.append(0)
|
|
return head
|
|
|
|
|
|
# MSX DEVICE_TABLE_ADDR ROM_TABLE_ID_ADDR MAX_DEVICE_ID MAX_ROM_ID
|
|
# DEVICE TABLE 8 BYT PER DEVICE (MAX)
|
|
# ROM TABLE 4 byt per ROM
|
|
|
|
# Pro cart potřebuji vyhledat device. Pokud je do adresniho prostoru musi mit existujici mapper. Pres IO port musi mit pouze dev, ale potrebuji io base.
|
|
# Pro slot potřebuji vyheldat device. Zde je pouze moznost mapperu. IO port nepotrebuji, ale pro jistotu ponechme cestu otevrenou
|
|
# Pro computer potřebuji vyhledat device, ale to je mapováno pouze do IO. Je to ciste dev. Potrebuji io base.
|
|
# Nové názvosloví (pro mně)
|
|
# Memory mapper -- Mapper pro mapování paměti/SRAM
|
|
# Device mapper -- Mapování z adresního prostoru na device
|
|
# Device -- Zařízení na IO portech
|
|
# Cartrige muze požádat o slot expander pro subsloty a v každém subslotu různé mappery typycky MFRSD
|
|
|
|
# Každé device se může namapovat na libovolný subslot a blok. Těchto namapování může být volitelné množství.
|
|
# popis parametrů
|
|
# 1. memory mapper / device mapper _id
|
|
# 2. device_id
|
|
# 3. rom_id_id
|
|
# 4. ram_block (16kb block)
|
|
# 5. sram_block (2kb block)
|
|
# 6. ram pattern
|
|
# 7. mode
|
|
# 8. param
|
|
# 9. subslot
|
|
# 10. last.
|
|
|
|
#MSX BBBB BBBB
|
|
|
|
def createBlock(node, sub_slot_id, rom_hashes=None):
|
|
def get_text(element, tag, default=None):
|
|
"""Helper function to extract text from an XML element."""
|
|
child = element.find(tag)
|
|
return child.text if child is not None else default
|
|
|
|
def add_block(block, block_start, block_count):
|
|
"""Helper function to append block information to blocks list."""
|
|
|
|
full_path = None
|
|
sha1 = get_text(block, 'SHA1')
|
|
|
|
if sha1 is not None:
|
|
if sha1 not in rom_hashes.keys():
|
|
print(f"Nenalezena ROM {get_text(block, 'filename')} ignore DEVICE !!!!")
|
|
return None
|
|
else:
|
|
full_path = rom_hashes[sha1]
|
|
|
|
return {
|
|
'mapper': get_text(block, 'mapper'),
|
|
'device': get_text(block, 'device'),
|
|
'SHA1': sha1,
|
|
'filename': get_text(block, 'filename'),
|
|
'sub_slot': sub_slot_id,
|
|
'block_start': block_start,
|
|
'block_count': block_count,
|
|
'full_path': full_path
|
|
}
|
|
|
|
blocks = []
|
|
node_blocks = node.findall("./block")
|
|
|
|
if not node_blocks:
|
|
block_start = 0
|
|
block_count = int(get_text(node, 'block_count', 4))
|
|
block = add_block(node, block_start, block_count)
|
|
if block:
|
|
blocks.append(block)
|
|
else:
|
|
for block in node_blocks:
|
|
block_start = int(block.attrib.get("start", 0))
|
|
block_count = int(get_text(block, 'block_count', 4 - block_start))
|
|
block_info = add_block(block, block_start, block_count)
|
|
if block_info:
|
|
blocks.append(block_info)
|
|
|
|
return blocks
|
|
|
|
def createDevice(node, rom_hashes=None):
|
|
blocks = []
|
|
sub_slots = node.findall("./secondary")
|
|
|
|
if not sub_slots:
|
|
blocks.extend(createBlock(node, 0, rom_hashes))
|
|
else:
|
|
for sub_slot in sub_slots:
|
|
sub_slot_id = sub_slot.attrib.get("slot", 0)
|
|
blocks.extend(createBlock(sub_slot, sub_slot_id, rom_hashes))
|
|
|
|
print(blocks)
|
|
|
|
def createFWpack(root, fileHandle) :
|
|
try :
|
|
for fw in root.findall("./fw"):
|
|
createDevice(fw, rom_hashes)
|
|
fw_name = fw.attrib["name"]
|
|
fw_filename = fw.find('filename').text if fw.find('filename') is not None else None
|
|
fw_SHA1 = fw.find('SHA1').text if fw.find('SHA1') is not None else None
|
|
fw_size = int(fw.find('size').text) if fw.find('size') is not None else None
|
|
fw_skip = int(fw.find('skip').text) if fw.find('skip') is not None else None
|
|
fw_device = fw.find('device').text if fw.find('device') is not None else None
|
|
fw_mapper = fw.find('mapper').text if fw.find('mapper') is not None else None
|
|
if fw_name in EXTENSIONS :
|
|
typ = EXTENSIONS.index(fw_name)
|
|
if fw_SHA1 is not None :
|
|
if fw_SHA1 in rom_hashes.keys() :
|
|
inFileName = rom_hashes[fw_SHA1]
|
|
fileSize = os.path.getsize(inFileName)
|
|
size = fw_size if fw_size is not None else fileSize
|
|
head = create_FW_block(typ, size >> 14)
|
|
fileHandle.write(head)
|
|
infile = open(inFileName, "rb")
|
|
if fw_skip is not None :
|
|
infile.seek(fw_skip, os.SEEK_SET)
|
|
if fw_size is not None :
|
|
fileHandle.write(infile.read(size))
|
|
else :
|
|
fileHandle.write(infile.read())
|
|
if size > fileSize :
|
|
fileHandle.write(bytes([0xFF] * (size - fileSize)))
|
|
else :
|
|
fileHandle.close()
|
|
raise Exception(f"Skip: {filename} Not found ROM {fw_filename} SHA1:{fw_SHA1}")
|
|
fileHandle.close()
|
|
return False
|
|
except Exception as e:
|
|
print(e)
|
|
return True
|
|
|
|
def getRefereced(secondary, reference) :
|
|
block = secondary.find((f'.//block[@start="{reference}"]'))
|
|
return getValues(block, None)
|
|
|
|
def getValues(block, secondary) :
|
|
values = {}
|
|
values['start'] = int(block.attrib["start"]) & 3
|
|
values['type'] = block.find('type').text if block.find('type') is not None else None
|
|
values['count'] = int(block.find('block_count').text) if block.find('block_count') is not None else 0
|
|
values['filename'] = block.find('filename').text if block.find('filename') is not None else None
|
|
values['SHA1'] = block.find('SHA1').text if block.find('SHA1') is not None else None
|
|
values['pattern'] = int(block.find('pattern').text) if block.find('pattern') is not None else 3
|
|
values['skip'] = int(block.find('skip').text) if block.find('skip') is not None else None
|
|
if secondary is not None and block.find('ref') is not None :
|
|
values['ref'] = getRefereced(secondary, block.find('ref').text)
|
|
|
|
return (values)
|
|
|
|
def createMSXpack(root, fileHandle) :
|
|
config = 0x00
|
|
try :
|
|
heads = []
|
|
msx_type_value = None
|
|
msx_type = root.find('type')
|
|
if msx_type is not None:
|
|
msx_type_value = msx_type.text
|
|
for primary in root.findall("./primary"):
|
|
primary_slot = primary.attrib["slot"]
|
|
for secondary in primary.findall("./secondary"):
|
|
secondary_slot = secondary.attrib["slot"]
|
|
for block in secondary.findall("./block"):
|
|
block_ref = block.find('ref').text if block.find('ref') is not None else None
|
|
values = getValues(block, secondary)
|
|
head = create_MSX_block(primary_slot, secondary_slot, values)
|
|
#print(' '.join([f'{byte:02X}' for byte in head[3:15]]) + " {0}/{1} ".format(primary_slot, secondary_slot) + str(values))
|
|
if block_ref is None :
|
|
fileHandle.write(head)
|
|
if values["SHA1"] is not None :
|
|
if values["SHA1"] in rom_hashes.keys() :
|
|
infile = open(rom_hashes[values["SHA1"]], "rb")
|
|
if values["skip"] is not None :
|
|
infile.seek(values["skip"], os.SEEK_SET)
|
|
write_size = fileHandle.write(infile.read(values['count'] * 16 * 1024))
|
|
if write_size < values['count'] * 16 * 1024 :
|
|
fileHandle.write(bytes([0xFF] * (values['count'] * 16 * 1024 - write_size)))
|
|
infile.close()
|
|
else :
|
|
fileHandle.close()
|
|
raise Exception(f"Skip: {filename} Not found ROM {0} SHA1:{1}",values["filename"],values["SHA1"])
|
|
else :
|
|
heads.append(head)
|
|
if int(secondary_slot) > 0 :
|
|
config = config | (0x1 << int(primary_slot))
|
|
for head in heads :
|
|
fileHandle.write(head)
|
|
|
|
for device in root.findall("./device"):
|
|
device_typ = device.attrib["typ"]
|
|
rom = device.find("./rom")
|
|
rom_size = 0
|
|
if rom is not None:
|
|
rom_name = rom.find('filename').text if rom.find('filename') is not None else None
|
|
rom_SHA1 = rom.find('sha1').text if rom.find('sha1') is not None else None
|
|
if rom_SHA1 is not None:
|
|
if rom_SHA1 in rom_hashes.keys() :
|
|
rom_size = os.path.getsize(rom_hashes[rom_SHA1]) >> 14
|
|
|
|
head = create_MSX_device(device_typ, rom_size);
|
|
fileHandle.write(head)
|
|
if rom_size > 0 :
|
|
infile = open(rom_hashes[rom_SHA1], "rb")
|
|
fileHandle.write(infile.read())
|
|
infile.close()
|
|
|
|
kbd_layout = root.find('kbd_layout')
|
|
if kbd_layout is not None:
|
|
values = {'type':"KBD LAYOUT", 'count':0}
|
|
head = create_MSX_block(0,0,values)
|
|
#print(' '.join([f'{byte:02X}' for byte in head[3:15]]) + " -/- " + str(values))
|
|
fileHandle.write(head)
|
|
fileHandle.write(base64.b64decode(kbd_layout.text))
|
|
|
|
config = config | ((get_msx_type_id(msx_type_value) & 0x3) << 4)
|
|
head = create_MSX_config(config)
|
|
#print(' '.join([f'{byte:02X}' for byte in head[3:15]]))
|
|
fileHandle.write(head)
|
|
fileHandle.close()
|
|
return False
|
|
except Exception as e:
|
|
print(e)
|
|
return True
|
|
|
|
# Traverse the XML directory and create blocks for each XML file
|
|
def parseDir(dir) :
|
|
for dirpath, dirnames, filenames in os.walk(dir):
|
|
for filename in filenames:
|
|
if filename.endswith('.xml'):
|
|
filepath = os.path.join(dirpath, filename)
|
|
filename_without_extension, extension = os.path.splitext(filename)
|
|
dir_save = "MSX" + dirpath[len(dir):]
|
|
output_filename = os.path.join(dir_save, filename_without_extension) + ".MSX"
|
|
if not os.path.exists(dir_save):
|
|
os.makedirs(dir_save)
|
|
|
|
tree = ET.parse(filepath)
|
|
root = tree.getroot()
|
|
outfile = open(output_filename, "wb")
|
|
error = True
|
|
# if root.tag == "msxConfig" :
|
|
# print(output_filename)
|
|
# error = createMSXpack(root, outfile)
|
|
if root.tag == "fwConfig" :
|
|
print(output_filename)
|
|
error = createFWpack(root, outfile)
|
|
if error :
|
|
outfile.close()
|
|
os.remove(output_filename)
|
|
|
|
|
|
# Traverse the ROM directory and save the hashes of the files in a dictionary
|
|
rom_hashes = {}
|
|
for dirpath, dirnames, filenames in os.walk(ROM_DIR):
|
|
for filename in filenames:
|
|
filepath = os.path.join(dirpath, filename)
|
|
rom_hashes[file_hash(filepath)] = filepath
|
|
|
|
|
|
parseDir(XML_DIR_COMP)
|
|
parseDir(XML_DIR_FW)
|
|
|
|
|
|
|
|
|