Files
MSX1_MiSTer/tools/CreateMSXpack/createDev.py
2025-05-31 12:01:06 +02:00

306 lines
13 KiB
Python

import os
import xml.etree.ElementTree as ET
import struct
import logging
from tools import load_constants, find_files_with_sha1, find_xml_files, convert_to_int_or_string, get_int_or_string_value, convert_to_8bit, convert_to_int, get_device_param
# Nastavení logování
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
ROM_DIR = 'ROM'
XML_DIR = 'Extension'
DIR_SAVE = 'MSX'
def create_block_entry(constants: dict, block_type: str, address: int, sha1: str = None, param1: int = 0, param2: int = 0, param3: int = 0, file_skip_bytes: int = 0, file_size: int = 0) -> dict:
block_entry = {
"typ": constants['conf']['BLOCK'],
"address": address,
"block_typ": constants['block'][block_type],
"param1": param1,
"param2": param2,
"param3": param3
}
if sha1:
block_entry['SHA1'] = sha1
block_entry['file_size'] = file_size
block_entry['file_skip_bytes'] = file_skip_bytes
return block_entry
def parse_fw_block(root: ET.Element, subslot: int, files_with_sha1: dict, constants: dict) -> list:
block = {}
result = []
start = int(root.attrib.get("start", 0))
count = int(root.attrib.get("count", 4))
offset = int(root.attrib.get("offset", -1))
fix_offset = int(root.attrib.get("fix_offset", -1))
for element in root:
if element.tag in ['SHA1', 'filename', 'device', 'mapper', 'sram', 'ram', 'device_param']:
block[element.tag] = get_int_or_string_value(element)
if element.tag == 'ram' :
pattern = 1;
if element.tag == 'device' :
device_port = convert_to_8bit(element.attrib.get('port'))
device_mask = convert_to_8bit(element.attrib.get('mask'))
device_param = convert_to_8bit(element.attrib.get('param','0'))
if device_port is not None:
if device_mask is None:
device_mask = 0xFF #Defaultni hodnota
if element.tag == 'SHA1' :
file_skip_bytes = convert_to_int(element.attrib.get('skip',0))
file_size = convert_to_int(element.attrib.get('size',0))
else:
logger.error(f"Tag name: {element.tag} SKIP. Not expected here")
if count > 4 - start:
count = 4 - start
address = ((subslot & 3) << 4) | ((start & 3) << 2) | ((count - 1) & 3)
if 'SHA1' in block:
if block['SHA1'] in files_with_sha1:
result.append(create_block_entry(constants, 'ROM', address, sha1=block['SHA1'], file_size=file_size, file_skip_bytes=file_skip_bytes))
else:
logger.warning(f"Missing ROM. SHA1: {block['SHA1']}")
if 'ram' in block:
size = convert_to_int(block['ram'])
if size :
result.append(create_block_entry(constants, 'RAM', address, param1=size//(1024 * 16), param2 = pattern)) # RAM v 16 kB blocích
else :
logger.warning("SRAM size not defined correctly")
if 'mapper' in block:
if block['mapper'] in constants['mapper']:
param2 = 0
if offset > -1 and offset < 4 :
param2 = 0x80 + offset
if fix_offset > -1 and fix_offset < 4 :
param2 = 0x80 + 0x40 + fix_offset
result.append(create_block_entry(constants, 'MAPPER', address, param1=constants['mapper'][block['mapper']], param2=param2))
else:
logger.warning(f"Unknown mapper type: {block['mapper']} see file mapper.json")
if 'device' in block:
if block['device'] in constants['device']:
param_dev = block.get('device_param', 0)
device_id = constants['device'][block['device']]
param_dev = 0
parameter = 0
if (element.attrib):
param_dev = get_device_param(constants, block['device'], element.attrib)
result.append(create_block_entry(constants, 'DEVICE', address, param1=device_id, param2 = param_dev, param3=parameter))
if device_port is not None:
result.append(create_block_entry(constants, 'IO_DEVICE', address, param1=device_port, param2 = device_mask, param3 = device_param))
else:
logger.warning(f"Unknown device type: {block['device']}")
if 'sram' in block:
size = convert_to_int(block['sram'])
if size :
result.append(create_block_entry(constants, 'SRAM', address, param1=size//1024)) # SRAM v 1 kB
else :
logger.warning("SRAM size not defined correctly")
return result
def parse_fw_secondary(root: ET.Element, subslot: int, files_with_sha1: dict, constants: dict) -> list:
blocks = []
for element in root:
if element.tag == 'block':
blocks.extend(parse_fw_block(element, subslot, files_with_sha1, constants))
return blocks
def parse_fw(name: str, root: ET.Element, files_with_sha1: dict, constants: dict) -> list:
results = []
for element in root:
if element.tag == 'secondary':
slot = int(element.attrib["slot"])
results.extend(parse_fw_secondary(element, slot, files_with_sha1, constants))
elif element.tag == 'block':
results.extend(parse_fw_block(element, 0, files_with_sha1, constants))
elif element.tag == 'io_device':
pass # Placeholder for future extensions
elif element.tag == 'expander':
if element.text == 'expander_wo' :
expander = 3
else :
expander = 1
result = []
result.append(create_block_entry(constants, 'EXPANDER', 0, param1=expander))
results.extend(result)
else:
logger.warning(f"Tag name: {element.tag} SKIP. Not expected here")
return results
def parse_fw_config(root: ET.Element, files_with_sha1: dict, constants: dict) -> dict:
results = {}
for element in root:
if element.tag == 'fw':
name = element.attrib["name"]
if name not in constants['cart_dev']:
logger.warning(f"FW name: {name} SKIP. Not in cart_device.json")
continue
results[name] = parse_fw(name, element, files_with_sha1, constants)
else:
logger.warning(f"Unknown tag: {element.tag} SKIP.")
continue
return results
def prepare_roms(config: dict, files_with_sha1: dict) -> dict:
roms = {}
start = 0
id = 0
try:
with open('ROM.bin', 'wb') as rom_file:
for key in config:
for block in config[key]:
if 'SHA1' in block:
if block['SHA1'] not in roms:
filename = files_with_sha1[block['SHA1']]
file_size = block["file_size"]
file_skip_bytes = block["file_skip_bytes"]
if file_size == 0 :
file_size = os.path.getsize(filename)
file_blocks = (file_size + 16383) // 16384
with open(filename, 'rb') as source_file:
if file_skip_bytes > 0:
source_file.seek(file_skip_bytes)
data = source_file.read(file_size)
rom_file.write(data)
current_size = len(data)
padding_needed = ((current_size + 16383) // 16384) * 16384 - current_size
if padding_needed > 0:
rom_file.write(b'\xff' * padding_needed)
rom_name = f'{block["file_skip_bytes"]}#{block["file_size"]}#' + block['SHA1']
roms[rom_name] = {"start": start, "blocks": file_blocks, "id": id}
id += 1
start += file_blocks * 16384
return roms
except IOError as e:
logger.error(f"Error writing to ROM.bin: {e}")
return {}
def set_address(address_array: bytearray, index: int, address: int):
if index < 0 or index >= 256:
raise ValueError("Index must be in the range 0 to 255.")
start = index * 4
address_array[start:start+4] = struct.pack('I', address)
def init_address(address_array: bytearray, addr: int):
for i in range(256):
set_address(address_array, i, addr)
def create_fw_config(config: dict, constants: dict, roms: dict) -> tuple:
data = bytearray()
address_array = bytearray(256 * 4)
addr = 0x10 + len(address_array)
init_address(address_array, addr)
data += struct.pack('BBBBBB', constants['conf']['END'], 0, 0, 0, 0, 0)
logger.info(f"CONF addr:{addr:04X} < {constants['conf']['END']:02X} 00 00 00 00 00")
addr += 6
for key in config:
logger.info(f"NAME: {key} index: {constants['cart_dev'][key]:02X}")
set_address(address_array, constants['cart_dev'][key], addr)
for conf in config[key]:
conf['param1'] = conf.get('param1', 0)
conf['param2'] = conf.get('param2', 0)
conf['param3'] = conf.get('param3', 0)
if conf['typ'] == constants['conf']['BLOCK'] and conf['block_typ'] == constants['block']['ROM']:
if 'SHA1' in conf :
sha1_name = f'{conf["file_skip_bytes"]}#{conf["file_size"]}#' + conf['SHA1']
if sha1_name in roms:
conf['param1'] = roms[sha1_name]['id']
conf['param2'] = (roms[sha1_name]['blocks'] >> 8) & 0xff
conf['param3'] = roms[sha1_name]['blocks'] & 0xff
else:
logger.warning(f"Missing SHA1 in ROM configuration or SHA1 not found in ROMs: {conf}")
continue
# Formátování číselných parametrů pro logování
param1_str = f"{conf['param1']:02X}" if isinstance(conf['param1'], int) else str(conf['param1'])
param2_str = f"{conf['param2']:02X}" if isinstance(conf['param2'], int) else str(conf['param2'])
param3_str = f"{conf['param3']:02X}" if isinstance(conf['param3'], int) else str(conf['param3'])
logger.info(f"CONF addr:{addr:04X} < {conf['typ']:02X} {conf['address']:02X} {conf['block_typ']:02X} {param1_str} {param2_str} {param3_str}")
data += struct.pack('BBBBBB', conf['typ'], conf['address'], conf['block_typ'], conf['param1'], conf['param2'], conf['param3'])
addr += 6
data += struct.pack('BBBBBB', constants['conf']['END'], 0, 0, 0, 0, 0)
addr += 6
return address_array, data, addr
def save_fw_config(config: dict, file_name: str, path: str, files_with_sha1: dict, constants: dict, roms: dict, table: bytearray, data: bytearray, rom_start_addr: int):
file_path = os.path.join(DIR_SAVE, path)
os.makedirs(file_path, exist_ok=True)
file_path = os.path.join(file_path, file_name + '.msx')
try:
with open(file_path, "wb") as outfile:
head = struct.pack('BBBBI', ord('M'), ord('s'), ord('X'), 0, rom_start_addr)
outfile.write(head)
outfile.write(b'\x00' * 8)
outfile.write(table)
outfile.write(data)
addr_rom_start = rom_start_addr + 4 * len(roms)
rom_table = bytearray()
for rom in roms:
rom_table += struct.pack('I', addr_rom_start + roms[rom]['start'])
outfile.write(rom_table)
with open('ROM.bin', 'rb') as rom_file:
rom_content = rom_file.read()
outfile.write(rom_content)
os.remove('ROM.bin')
except IOError as e:
logger.error(f"Error saving MSX config to {file_path}: {e}")
def create_fw_conf(file_name: str, path: str, files_with_sha1: dict, constants: dict):
file_path = os.path.join(XML_DIR, path, file_name + '.xml')
try:
tree = ET.parse(file_path)
root = tree.getroot()
if root.tag != "fwConfig":
logger.warning(f"File {file_path} is not a valid msxConfig XML. Skipping.")
return
config = parse_fw_config(root, files_with_sha1, constants)
roms = prepare_roms(config, files_with_sha1)
table, data, rom_start_addr = create_fw_config(config, constants, roms)
save_fw_config(config, file_name, path, files_with_sha1, constants, roms, table, data, rom_start_addr)
except (ET.ParseError, FileNotFoundError) as e:
logger.error(f"Error processing file {file_path}: {e}")
files_with_sha1 = find_files_with_sha1(ROM_DIR)
constants = load_constants()
xml_files = find_xml_files(XML_DIR)
for file_name, path in xml_files:
print(f"NAME: {file_name}")
create_fw_conf(file_name, path, files_with_sha1, constants)