Created
March 11, 2019 14:23
-
-
Save tycho/9a4a4bafdfd4c4f73f5dcc50a572fd19 to your computer and use it in GitHub Desktop.
Hatari config generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import copy | |
import os | |
import re | |
from pprint import pprint | |
def script_dir(): | |
return os.path.dirname(os.path.realpath(__file__)) | |
cfg_template = r''' | |
[Log] | |
sLogFileName = stderr | |
sTraceFileName = stderr | |
nExceptionDebugMask = 259 | |
nTextLogLevel = 4 | |
nAlertDlgLogLevel = 1 | |
bConfirmQuit = FALSE | |
bNatFeats = FALSE | |
bConsoleWindow = FALSE | |
[Debugger] | |
nNumberBase = 10 | |
nDisasmLines = 8 | |
nMemdumpLines = 8 | |
nDisasmOptions = 15 | |
bDisasmUAE = FALSE | |
[Screen] | |
nMonitorType = {nMonitorType} | |
nFrameSkips = 5 | |
bFullScreen = TRUE | |
bKeepResolution = TRUE | |
bResizable = TRUE | |
bAllowOverscan = FALSE | |
nSpec512Threshold = 1 | |
nForceBpp = 0 | |
bAspectCorrect = TRUE | |
bUseExtVdiResolutions = {bUseExtVdiResolutions} | |
nVdiWidth = {nVdiWidth} | |
nVdiHeight = {nVdiHeight} | |
nVdiColors = {nVdiColors} | |
bMouseWarp = TRUE | |
bShowStatusbar = FALSE | |
bShowDriveLed = FALSE | |
bCrop = FALSE | |
bForceMax = FALSE | |
nMaxWidth = 1920 | |
nMaxHeight = 1080 | |
bUseSdlRenderer = TRUE | |
nRenderScaleQuality = {nRenderScaleQuality} | |
bUseVsync = TRUE | |
[Joystick0] | |
nJoystickMode = 0 | |
bEnableAutoFire = FALSE | |
bEnableJumpOnFire2 = FALSE | |
nJoyId = 1 | |
kUp = Up | |
kDown = Down | |
kLeft = Left | |
kRight = Right | |
kFire = Right Ctrl | |
[Joystick1] | |
nJoystickMode = 1 | |
bEnableAutoFire = FALSE | |
bEnableJumpOnFire2 = FALSE | |
nJoyId = 0 | |
kUp = Up | |
kDown = Down | |
kLeft = Left | |
kRight = Right | |
kFire = Right Ctrl | |
[Joystick2] | |
nJoystickMode = 0 | |
bEnableAutoFire = FALSE | |
bEnableJumpOnFire2 = FALSE | |
nJoyId = 2 | |
kUp = Up | |
kDown = Down | |
kLeft = Left | |
kRight = Right | |
kFire = Right Ctrl | |
[Joystick3] | |
nJoystickMode = 0 | |
bEnableAutoFire = FALSE | |
bEnableJumpOnFire2 = FALSE | |
nJoyId = 3 | |
kUp = Up | |
kDown = Down | |
kLeft = Left | |
kRight = Right | |
kFire = Right Ctrl | |
[Joystick4] | |
nJoystickMode = 0 | |
bEnableAutoFire = FALSE | |
bEnableJumpOnFire2 = FALSE | |
nJoyId = 4 | |
kUp = Up | |
kDown = Down | |
kLeft = Left | |
kRight = Right | |
kFire = Right Ctrl | |
[Joystick5] | |
nJoystickMode = 0 | |
bEnableAutoFire = FALSE | |
bEnableJumpOnFire2 = FALSE | |
nJoyId = 5 | |
kUp = Up | |
kDown = Down | |
kLeft = Left | |
kRight = Right | |
kFire = Right Ctrl | |
[Keyboard] | |
bDisableKeyRepeat = TRUE | |
nKeymapType = 0 | |
szMappingFileName = | |
[KeyShortcutsWithMod] | |
kOptions = O | |
kFullScreen = F | |
kBorders = B | |
kMouseMode = M | |
kColdReset = C | |
kWarmReset = R | |
kScreenShot = G | |
kBossKey = I | |
kCursorEmu = J | |
kFastForward = X | |
kRecAnim = A | |
kRecSound = Y | |
kSound = S | |
kPause = | |
kDebugger = Pause | |
kQuit = Q | |
kLoadMem = L | |
kSaveMem = K | |
kInsertDiskA = D | |
kSwitchJoy0 = F1 | |
kSwitchJoy1 = F2 | |
kSwitchPadA = F3 | |
kSwitchPadB = F4 | |
[KeyShortcutsWithoutMod] | |
kOptions = F12 | |
kFullScreen = F11 | |
kBorders = | |
kMouseMode = | |
kColdReset = | |
kWarmReset = | |
kScreenShot = | |
kBossKey = | |
kCursorEmu = | |
kFastForward = | |
kRecAnim = | |
kRecSound = | |
kSound = | |
kPause = Pause | |
kDebugger = | |
kQuit = | |
kLoadMem = | |
kSaveMem = | |
kInsertDiskA = | |
kSwitchJoy0 = | |
kSwitchJoy1 = | |
kSwitchPadA = | |
kSwitchPadB = | |
[Sound] | |
bEnableMicrophone = TRUE | |
bEnableSound = TRUE | |
bEnableSoundSync = FALSE | |
nPlaybackFreq = 44100 | |
nSdlAudioBufferSize = 0 | |
szYMCaptureFileName = {hataridir}/hatari.wav | |
YmVolumeMixing = 1 | |
[Memory] | |
nMemorySize = {nMemorySize} | |
nTTRamSize = {nTTRamSize} | |
bAutoSave = FALSE | |
szMemoryCaptureFileName = {hataridir}/hatari.sav | |
szAutoSaveFileName = {hataridir}/auto.sav | |
[Floppy] | |
bAutoInsertDiskB = TRUE | |
FastFloppy = TRUE | |
EnableDriveA = {EnableDriveA} | |
DriveA_NumberOfHeads = 2 | |
EnableDriveB = {EnableDriveB} | |
DriveB_NumberOfHeads = 2 | |
nWriteProtection = 1 | |
szDiskAZipPath = | |
szDiskAFileName = | |
szDiskBZipPath = | |
szDiskBFileName = | |
szDiskImageDirectory = {hataridir}/DISKS | |
[HardDisk] | |
nGemdosDrive = 0 | |
bBootFromHardDisk = FALSE | |
bUseHardDiskDirectory = TRUE | |
szHardDiskDirectory = {hataridir}/GEMDOS | |
nGemdosCase = 0 | |
nWriteProtection = 0 | |
bFilenameConversion = FALSE | |
bGemdosHostTime = FALSE | |
bUseHardDiskImage = FALSE | |
szHardDiskImage = {hataridir}/harddisk.hd | |
bUseIdeMasterHardDiskImage = FALSE | |
bUseIdeSlaveHardDiskImage = FALSE | |
szIdeMasterHardDiskImage = {hataridir} | |
szIdeSlaveHardDiskImage = {hataridir} | |
[ROM] | |
szTosImageFileName = {hataridir}/{ROM} | |
bPatchTos = TRUE | |
szCartridgeImageFileName = | |
[RS232] | |
bEnableRS232 = FALSE | |
szOutFileName = /dev/modem | |
szInFileName = /dev/modem | |
[Printer] | |
bEnablePrinting = FALSE | |
szPrintToFileName = {hataridir}/hatari.prn | |
[Midi] | |
bEnableMidi = FALSE | |
sMidiInFileName = /dev/snd/midiC1D0 | |
sMidiOutFileName = /dev/snd/midiC1D0 | |
[System] | |
nCpuLevel = {nCpuLevel} | |
nCpuFreq = {nCpuFreq} | |
bCompatibleCpu = {bCompatibleCpu} | |
nModelType = {nModelType} | |
bBlitter = {bBlitter} | |
nDSPType = 0 | |
bPatchTimerD = TRUE | |
bFastBoot = FALSE | |
bFastForward = FALSE | |
bAddressSpace24 = {bAddressSpace24} | |
bCycleExactCpu = {bCycleExactCpu} | |
n_FPUType = {n_FPUType} | |
bSoftFloatFPU = FALSE | |
bMMU = {bMMU} | |
VideoTiming = {VideoTiming} | |
[Video] | |
AviRecordVcodec = 2 | |
AviRecordFps = 0 | |
AviRecordFile = {hataridir}/hatari.avi | |
''' | |
machine_base_def = { | |
'id': {}, | |
'patches': { | |
'nModelType': '0', | |
'bBlitter': 'TRUE', | |
'bCompatibleCpu': 'TRUE', | |
'nMemorySize': '4096', | |
'nTTRamSize': '0', | |
'nCpuLevel': '0', | |
'nCpuFreq': '8', | |
'bAddressSpace24': 'TRUE', | |
'bCycleExactCpu': 'TRUE', | |
'n_FPUType': '0', | |
'bMMU': 'FALSE', | |
'VideoTiming': '3', | |
'nMonitorType': '1', | |
'bUseExtVdiResolutions': 'FALSE', | |
'nVdiWidth': '640', | |
'nVdiHeight': '480', | |
'nVdiColors': '2', | |
'nRenderScaleQuality': '0', | |
'EnableDriveA': 'TRUE', | |
'EnableDriveB': 'TRUE', | |
'hataridir': script_dir(), | |
}, | |
} | |
machine_variant_defs = [ | |
{ | |
# Unmodified | |
'id': { | |
'variant': None, | |
}, | |
'patches': {}, | |
}, | |
{ | |
'id': { | |
'variant': 'Fast', | |
}, | |
'patches': { | |
'bCompatibleCpu': 'FALSE', | |
'bCycleExactCpu': 'FALSE', | |
'nCpuLevel': '2', | |
'nCpuFreq': '32', | |
'n_FPUType': '68882', | |
}, | |
}, | |
{ | |
'id': { | |
'variant': 'FastVDI', | |
}, | |
'patches': { | |
'bCompatibleCpu': 'FALSE', | |
'bCycleExactCpu': 'FALSE', | |
'nCpuLevel': '2', | |
'nCpuFreq': '32', | |
'n_FPUType': '68882', | |
'bUseExtVdiResolutions': 'TRUE', | |
'nVdiWidth': '768', | |
'nVdiHeight': '432', | |
'nRenderScaleQuality': '1', | |
}, | |
}, | |
{ | |
'id': { | |
'variant': 'FastMono', | |
}, | |
'patches': { | |
'bCycleExactCpu': 'FALSE', | |
'nCpuLevel': '2', | |
'nCpuFreq': '32', | |
'n_FPUType': '68882', | |
'nMonitorType': '0', | |
'nRenderScaleQuality': '1', | |
}, | |
}, | |
{ | |
'id': { | |
'variant': 'FastMonoVDI', | |
}, | |
'patches': { | |
'bCycleExactCpu': 'FALSE', | |
'nCpuLevel': '2', | |
'nCpuFreq': '32', | |
'n_FPUType': '68882', | |
'bUseExtVdiResolutions': 'TRUE', | |
'nVdiWidth': '1920', | |
'nVdiHeight': '1080', | |
'nVdiColors': '0', | |
}, | |
}, | |
] | |
machine_defs = [ | |
{ | |
'id': { | |
'model': 'ST', | |
}, | |
'patches': { | |
'nMemorySize': '512', | |
'bBlitter': 'FALSE', | |
}, | |
}, | |
{ | |
'id': { | |
'model': 'MegaST', | |
}, | |
'patches': { | |
'nModelType': '1', | |
'nMemorySize': '2048', | |
}, | |
}, | |
{ | |
'id': { | |
'model': 'STE', | |
}, | |
'patches': { | |
'nModelType': '2', | |
}, | |
}, | |
{ | |
'id': { | |
'model': 'MegaSTE', | |
}, | |
'patches': { | |
'nCpuFreq': '16', | |
'nModelType': '3', | |
}, | |
}, | |
{ | |
'id': { | |
'model': 'TT', | |
}, | |
'patches': { | |
'nModelType': '4', | |
'nTTRamSize': '8192', | |
'nCpuLevel': '3', | |
'nCpuFreq': '32', | |
'bAddressSpace24': 'FALSE', | |
'bCycleExactCpu': 'FALSE', | |
'n_FPUType': '68882', | |
'bMMU': 'TRUE', | |
'VideoTiming': '0', | |
}, | |
}, | |
{ | |
'id': { | |
'model': 'Falcon', | |
}, | |
'patches': { | |
'nModelType': '5', | |
'nTTRamSize': '8192', | |
'nCpuLevel': '3', | |
'nCpuFreq': '16', | |
'bAddressSpace24': 'FALSE', | |
'n_FPUType': '68040', | |
'VideoTiming': '1', | |
}, | |
}, | |
#{ | |
# 'id': { | |
# 'model': 'Sparrow', | |
# }, | |
# 'patches': { | |
# 'nModelType': '5', | |
# 'nTTRamSize': '8192', | |
# 'nCpuLevel': '3', | |
# 'nCpuFreq': '16', | |
# 'n_FPUType': '68882', | |
# }, | |
#}, | |
] | |
rom_sizes = { | |
196608: ['ST', 'MegaST'], | |
262144: ['STE', 'MegaSTE', 'Sparrow'], | |
512788: ['Falcon'], | |
524287: ['Falcon'], | |
524288: ['TT', 'Falcon'], | |
} | |
rom_languages = { | |
0x00: 'US', | |
0x03: 'DE', | |
0x05: 'FR', | |
0x07: 'UK', | |
0x09: 'ES', | |
0x0B: 'IT', | |
0x0D: 'SE', | |
0x11: 'SG', | |
0x15: 'FI', | |
0x17: 'NO', | |
0x1F: 'CZ', | |
0x27: 'RU', | |
0x3F: 'GR', | |
0xFF: None, # Multi-language | |
} | |
def rom_version_compatible(machine, rom_patch): | |
vendor = rom_patch['id']['rom_vendor'] | |
version = rom_patch['id']['rom_version'] | |
size = rom_patch['id']['rom_size'] | |
if machine not in rom_sizes.get(size, []): | |
return False | |
if vendor == 'EmuTOS': | |
# Should work on anything. | |
return True | |
rom_ranges = { | |
'STE': ((1,6), (1,999)), | |
'MegaSTE': ((2,0), (2,6)), | |
'Sparrow': ((2,7), (2,7)), | |
'TT': ((3,0), (3,999)), | |
'Falcon': ((4,0), (4,999)), | |
} | |
begin, end = rom_ranges.get(machine, ((0,0), (999,999))) | |
return begin <= version <= end | |
class InvalidROMError(Exception): | |
pass | |
def _probe_rom_version(rom): | |
vendor = None | |
with open(rom, 'rb') as fd: | |
header = fd.read(4) | |
fd.seek(29, 0) | |
langid = ord(fd.read(1)) | |
fd.seek(44, 0) | |
try: | |
vendor = fd.read(4).decode('utf-8') | |
except UnicodeDecodeError: | |
pass | |
if vendor == 'ETOS': | |
vendor = 'EmuTOS' | |
else: | |
vendor = 'TOS' | |
# TOS 4.92 has a different header format | |
if header == b'\x46\xfc\x27\x00': | |
return ('TOS', (4, 92), None) | |
if header[0] != 0x60: | |
return None | |
if header[1] not in (0x1E, 0x2E): | |
return None | |
version = int(hex(header[2])[2:]), int(hex(header[3])[2:]) | |
try: | |
lang = rom_languages[langid] | |
except KeyError: | |
raise InvalidROMError() | |
return vendor, version, lang | |
def rom_identify(path, rom): | |
rom_vendor, rom_version, rom_language = _probe_rom_version(os.path.join(path, rom)) | |
rom_name = '%s-%d.%02d' % (rom_vendor, rom_version[0], rom_version[1]) | |
if rom_language is not None: | |
rom_name += '-' + rom_language | |
return rom_name, rom_vendor, rom_version, rom_language | |
def scan_roms(): | |
roms = [] | |
for path, dirnames, filenames in os.walk('TOS'): | |
for filename in filenames: | |
stat = os.stat(os.path.join(path, filename)) | |
if stat.st_size not in rom_sizes: | |
print("Unrecognized ROM type %s" % (filename,)) | |
continue | |
try: | |
name, vendor, version, language = rom_identify(path, filename) | |
except InvalidROMError: | |
print("Unrecognized ROM identity %s" % (filename,)) | |
continue | |
rom_patch = { | |
'id': { | |
'rom': name, | |
'rom_vendor': vendor, | |
'rom_version': version, | |
'rom_size': stat.st_size, | |
'rom_language': language, | |
}, | |
'patches': { | |
'ROM': os.path.join(path, filename), | |
}, | |
} | |
if vendor == 'EmuTOS': | |
# EmuTOS will repeatedly prompt for being unable to read a floppy | |
# disk when exiting an app (e.g. QuickIndex) | |
rom_patch['patches'].update({ | |
'EnableDriveA': 'FALSE', | |
'EnableDriveB': 'FALSE', | |
}) | |
roms.append(rom_patch) | |
return roms | |
def combine(base, variants): | |
for machine_def in variants: | |
machine_base = copy.deepcopy(base) | |
machine_base['id'].update(machine_def['id']) | |
machine_base['patches'].update(machine_def['patches']) | |
yield machine_base | |
def config_filename(index, machine): | |
machine_id = machine['id'] | |
rom_name = machine_id['rom'] | |
rom_vendor = machine_id['rom_vendor'] | |
rom_version = machine_id['rom_version'] | |
model = machine_id['model'] | |
patches = machine['patches'] | |
variant = '' | |
if machine_id['variant'] is not None: | |
variant = '-' + machine_id['variant'] | |
# Some TOS ROMs do not like extended VDI resolutions | |
if patches['bUseExtVdiResolutions'] == 'TRUE': | |
if rom_vendor == 'EmuTOS' and int(patches['nModelType']) < 2: | |
return None | |
if rom_version is not None and rom_version <= (2, 5): | |
return None | |
if rom_version is not None: | |
# TOS <= 1.04 don't like CPUs other than the stock 68000 | |
if (rom_version <= (1, 4) or rom_version == (1, 62)) and int(patches['nCpuLevel']) > 0: | |
return None | |
config_name = 'hatari-%s-%s%s-%s.cfg' % ( | |
index, model, variant, rom_name) | |
return config_name | |
def main(): | |
roms = scan_roms() | |
# Combine machine base with Atari models. | |
for index, machine_base in enumerate(combine(machine_base_def, machine_defs)): | |
# Combine models with variant definitions | |
for machine_variant in combine(machine_base, machine_variant_defs): | |
machine_id = machine_variant['id'] | |
model = machine_id['model'] | |
# Find compatible ROM patches | |
rom_patches = [] | |
for rom in roms: | |
if rom_version_compatible(model, rom): | |
rom_patches.append(rom) | |
# Combine model variants with ROM definitions | |
for machine in combine(machine_variant, rom_patches): | |
config_name = config_filename(index, machine) | |
if config_name is None: | |
continue | |
print("Writing configuration %s..." % (config_name,)) | |
with open(config_name, 'wt') as config: | |
config.write(cfg_template.format(**machine['patches'])) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment