Skip to content

Instantly share code, notes, and snippets.

@tycho
Created March 11, 2019 14:23
Show Gist options
  • Save tycho/9a4a4bafdfd4c4f73f5dcc50a572fd19 to your computer and use it in GitHub Desktop.
Save tycho/9a4a4bafdfd4c4f73f5dcc50a572fd19 to your computer and use it in GitHub Desktop.
Hatari config generator
#!/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