import { DebugProtocol } from 'vscode-debugprotocol/lib/debugProtocol' ; import { Utility } from './misc/utility' ; import * as fs from 'fs' ; import { UnifiedPath } from './misc/unifiedpath' ; import { BankType } from './remotes/zsimulator/simmemory' ; import * as hjoin from '@bartificer/human-join' ; export interface AsmConfigBase { path : string ; srcDirs : Array < string > ; excludeFiles : Array < string > ; } export interface SjasmplusConfig extends AsmConfigBase { disableBanking : boolean ; } export interface Z80asmConfig extends AsmConfigBase { } export interface Z88dkConfig extends AsmConfigBase { mainFile : string ; mapFile : string ; } export interface Formatting { registerVar : Array < string > ; registerHover : Array < string > ; bigValues : string ; smallValues : string ; watchByte : string ; watchWord : string ; stackVar : string ; } export interface LoadObj { path : string ; start : string ; } export interface ZrcpType { hostname : string ; port : number ; skipInterrupt : boolean ; loadDelay : number ; resetOnLaunch : boolean ; socketTimeout : number ; } export interface CSpectType { hostname : string ; port : number ; socketTimeout : number ; } export interface ZxNextSocketType { hostname : string ; port : number ; socketTimeout : number ; } export interface CustomCodeType { debug : boolean ; jsPath : string ; uiPath : string ; timeStep : number ; } /**
 * The user can define a custom memory.
 * Note: It is only possible to define 64k of memory.
 * No paging mechanism.
 */ export interface CustomMemoryType { numberOfBanks : number ; banks : Map < string , string > ; } export interface ZSimType { zxKeyboard : boolean ; zxInterface2Joy : boolean ; kempstonJoy : boolean ; visualMemory : boolean ; ulaScreen : boolean ; zxBorderWidth : number ; zxBeeper : boolean ; audioSampleRate : number ; memoryModel : string ; /** A user defined memory.
     * "customMemory": {
     *	"numberOfBanks": 4,
     *		"banks": {
     *			"0": "ROM",
     *			"1": "RAM"
     *		}
     *	},
     */ customMemory : { numberOfBanks : number ; banks : Map < string , string > ; } ; cpuLoadInterruptRange : number ; Z80N : boolean ; vsyncInterrupt : boolean ; cpuFrequency : number ; limitSpeed : boolean ; updateFrequency : number ; defaultPortIn : number ; customCode : CustomCodeType ; } /**
 * See also package.json.
 * The configuration parameters for the zesarux debugger.
 */ export interface SettingsParameters extends DebugProtocol . LaunchRequestArguments { remoteType : string ; zrcp : ZrcpType ; cspect : CSpectType ; zsim : ZSimType ; zxnext : ZxNextSocketType ; unitTests : false ; rootFolder : string ; sjasmplus : Array < SjasmplusConfig > ; z80asm : Array < Z80asmConfig > ; z88dk : Array < Z88dkConfig > ; smallValuesMaximum : number ; disassemblerArgs : { numberOfLines : number ; esxdosRst : boolean ; } ; tmpDir : string ; topOfStack : string ; execAddress : string ; load : string ; loadObjs : Array < LoadObj > ; startAutomatically : boolean ; commandsAfterLaunch : Array < string > ; history : { reverseDebugInstructionCount : number ; spotCount : number ; spotShowRegisters : boolean ; codeCoverageEnabled : boolean ; } ; formatting : Formatting ; memoryViewer : { addressColor : string ; bytesColor : string ; asciiColor : string ; addressHoverFormat : string ; valueHoverFormat : string ; registerPointerColors : Array < string > ; registersMemoryView : Array < string > ; } ; tabSize : number ; unitTestTimeout : number ; } export class Settings { protected static MAX_HISTORY_SPOT_COUNT = 20 ; public static launch : SettingsParameters ; param launchCfg param rootFolder returns static Init ( launchCfg ) { if ( ! launchCfg ) { launchCfg = { remoteType : < any > undefined , zrcp : < any > undefined , cspect : < any > undefined , zsim : < any > undefined , zxnext : < any > undefined , unitTests : < any > undefined , rootFolder : < any > undefined , sjasmplus : < any > undefined , z80asm : < any > undefined , z88dk : < any > undefined , smallValuesMaximum : < any > undefined , disassemblerArgs : < any > undefined , tmpDir : < any > undefined , topOfStack : < any > undefined , execAddress : < any > undefined , load : < any > undefined , loadObjs : < any > undefined , startAutomatically : < any > undefined , commandsAfterLaunch : < any > undefined , history : < any > undefined , formatting : < any > undefined , memoryViewer : < any > undefined , tabSize : < any > undefined , unitTestTimeout : < any > undefined , } ; } const rootFolder = launchCfg . rootFolder || '' ; if ( launchCfg . unitTests == undefined ) launchCfg . unitTests = false ; const unitTests = launchCfg . unitTests ; if ( ! launchCfg . zrcp ) launchCfg . zrcp = { } as ZrcpType ; if ( launchCfg . zrcp . hostname == undefined ) launchCfg . zrcp . hostname = 'localhost' ; if ( launchCfg . zrcp . port == undefined ) launchCfg . zrcp . port = 10000 ; if ( launchCfg . zrcp . loadDelay == undefined ) { const platform = process . platform ; let delay = 0 ; if ( platform == 'win32' ) delay = 100 ; launchCfg . zrcp . loadDelay = delay ; } if ( launchCfg . zrcp . resetOnLaunch == undefined ) launchCfg . zrcp . resetOnLaunch = true ; if ( ! launchCfg . zrcp . socketTimeout ) launchCfg . zrcp . socketTimeout = 5 ; if ( ! launchCfg . cspect ) launchCfg . cspect = { } as CSpectType ; if ( launchCfg . cspect . hostname == undefined ) launchCfg . cspect . hostname = 'localhost' ; if ( launchCfg . cspect . port == undefined ) launchCfg . cspect . port = 11000 ; if ( ! launchCfg . cspect . socketTimeout ) launchCfg . cspect . socketTimeout = 5 ; if ( ! launchCfg . zsim ) launchCfg . zsim = { } as ZSimType ; if ( launchCfg . zsim . zxKeyboard == undefined ) launchCfg . zsim . zxKeyboard = false ; if ( launchCfg . zsim . zxInterface2Joy == undefined ) launchCfg . zsim . zxInterface2Joy = false ; if ( launchCfg . zsim . kempstonJoy == undefined ) launchCfg . zsim . kempstonJoy = false ; if ( launchCfg . zsim . ulaScreen == undefined ) launchCfg . zsim . ulaScreen = false ; if ( launchCfg . zsim . zxBorderWidth == undefined || ! launchCfg . zsim . ulaScreen ) launchCfg . zsim . zxBorderWidth = 0 ; if ( launchCfg . zsim . zxBeeper == undefined ) launchCfg . zsim . zxBeeper = false ; if ( launchCfg . zsim . audioSampleRate == undefined ) launchCfg . zsim . audioSampleRate = 22050 ; if ( launchCfg . zsim . cpuLoadInterruptRange == undefined ) launchCfg . zsim . cpuLoadInterruptRange = 1 ; if ( launchCfg . zsim . visualMemory == undefined ) launchCfg . zsim . visualMemory = true ; if ( launchCfg . zsim . memoryModel == undefined ) launchCfg . zsim . memoryModel = "RAM" ; launchCfg . zsim . memoryModel = launchCfg . zsim . memoryModel . toUpperCase ( ) ; if ( launchCfg . zsim . Z80N == undefined ) launchCfg . zsim . Z80N = false ; if ( launchCfg . zsim . vsyncInterrupt == undefined ) launchCfg . zsim . vsyncInterrupt = false ; if ( launchCfg . zsim . cpuFrequency == undefined ) launchCfg . zsim . cpuFrequency = 3500000.0 ; if ( launchCfg . zsim . limitSpeed == undefined ) launchCfg . zsim . limitSpeed = true ; if ( launchCfg . zsim . updateFrequency == undefined ) launchCfg . zsim . updateFrequency = 10.0 ; if ( launchCfg . zsim . defaultPortIn == undefined ) launchCfg . zsim . defaultPortIn = 0xFF ; if ( launchCfg . zsim . updateFrequency < 5.0 ) launchCfg . zsim . updateFrequency = 5.0 ; else if ( launchCfg . zsim . updateFrequency > 100.0 ) launchCfg . zsim . updateFrequency = 100.0 ; if ( launchCfg . zsim . customCode == undefined ) { launchCfg . zsim . customCode = { } as any ; } if ( launchCfg . zsim . customCode . debug == undefined ) launchCfg . zsim . customCode . debug = false ; if ( launchCfg . zsim . customCode . jsPath != undefined ) { const path = UnifiedPath . getUnifiedPath ( launchCfg . zsim . customCode . jsPath ) ; launchCfg . zsim . customCode . jsPath = Utility . getAbsFilePath ( path , rootFolder ) ; } if ( launchCfg . zsim . customCode . uiPath != undefined ) { const path = UnifiedPath . getUnifiedPath ( launchCfg . zsim . customCode . uiPath ) ; launchCfg . zsim . customCode . uiPath = Utility . getAbsFilePath ( path , rootFolder ) ; } if ( launchCfg . zsim . customCode . timeStep == undefined ) { launchCfg . zsim . customCode . timeStep = Number . MAX_SAFE_INTEGER ; } if ( ! launchCfg . zxnext ) launchCfg . zxnext = { } as ZxNextSocketType ; if ( launchCfg . zxnext . hostname == undefined ) launchCfg . zxnext . hostname = 'localhost' ; if ( launchCfg . zxnext . port == undefined ) launchCfg . zxnext . port = 12000 ; if ( ! launchCfg . zxnext . socketTimeout ) launchCfg . zxnext . socketTimeout = 0.8 ; if ( launchCfg . sjasmplus ) { launchCfg . sjasmplus = launchCfg . sjasmplus . map ( fp => { const fpPath = UnifiedPath . getUnifiedPath ( fp . path ) ; const fpSrcDirs = UnifiedPath . getUnifiedPathArray ( fp . srcDirs ) ; const fpExclFiles = UnifiedPath . getUnifiedPathArray ( fp . excludeFiles ) ; const file = { path : undefined as any , srcDirs : fpSrcDirs || [ "" ] , excludeFiles : fpExclFiles || [ ] , disableBanking : fp . disableBanking || false } ; if ( fpPath ) file . path = Utility . getAbsFilePath ( fpPath , rootFolder ) ; return file ; } ) ; } if ( launchCfg . z80asm ) { launchCfg . z80asm = launchCfg . z80asm . map ( fp => { const fpPath = UnifiedPath . getUnifiedPath ( fp . path ) ; const fpSrcDirs = UnifiedPath . getUnifiedPathArray ( fp . srcDirs ) ; const fpExclFiles = UnifiedPath . getUnifiedPathArray ( fp . excludeFiles ) ; const file = { path : undefined as any , srcDirs : fpSrcDirs || [ "" ] , excludeFiles : fpExclFiles || [ ] } ; if ( fpPath ) file . path = Utility . getAbsFilePath ( fpPath , rootFolder ) ; return file ; } ) ; } if ( launchCfg . z88dk ) { launchCfg . z88dk = launchCfg . z88dk . map ( fp => { const fpPath = UnifiedPath . getUnifiedPath ( fp . path ) ; const fpSrcDirs = UnifiedPath . getUnifiedPathArray ( fp . srcDirs ) ; const fpMapFile = UnifiedPath . getUnifiedPath ( fp . mapFile ) ; const fpExclFiles = UnifiedPath . getUnifiedPathArray ( fp . excludeFiles ) ; const fpMainFile = UnifiedPath . getUnifiedPath ( fp . mainFile ) ; const file = { path : undefined as any , srcDirs : fpSrcDirs || [ "" ] , excludeFiles : fpExclFiles || [ ] , mainFile : fpMainFile || "" , mapFile : undefined as any } ; if ( fpPath ) file . path = Utility . getAbsFilePath ( fpPath , rootFolder ) ; if ( fpMapFile ) file . mapFile = Utility . getAbsFilePath ( fpMapFile ) ; return file ; } ) ; } if ( ! launchCfg . topOfStack ) launchCfg . topOfStack = '0x10000' ; if ( unitTests ) launchCfg . topOfStack = 'UNITTEST_STACK' ; if ( launchCfg . load ) { const uload = UnifiedPath . getUnifiedPath ( launchCfg . load ) ; launchCfg . load = Utility . getAbsFilePath ( uload , rootFolder ) ; } else launchCfg . load = '' ; if ( ! launchCfg . loadObjs ) launchCfg . loadObjs = [ ] ; for ( let loadObj of launchCfg . loadObjs ) { if ( loadObj . path ) { const loadObjPath = UnifiedPath . getUnifiedPath ( loadObj . path ) ; loadObj . path = Utility . getAbsFilePath ( loadObjPath , rootFolder ) ; } else loadObj . path = '' ; } if ( launchCfg . tmpDir == undefined ) launchCfg . tmpDir = '.tmp' ; launchCfg . tmpDir = UnifiedPath . getUnifiedPath ( launchCfg . tmpDir ) ; launchCfg . tmpDir = Utility . getAbsFilePath ( launchCfg . tmpDir , rootFolder ) ; if ( isNaN ( launchCfg . smallValuesMaximum ) ) launchCfg . smallValuesMaximum = 255 ; if ( launchCfg . disassemblerArgs == undefined ) launchCfg . disassemblerArgs = { numberOfLines : 10 , esxdosRst : false } ; if ( ! launchCfg . disassemblerArgs . hasOwnProperty ( "numberOfLines" ) ) launchCfg . disassemblerArgs . numberOfLines = 10 ; if ( launchCfg . disassemblerArgs . numberOfLines > 100 ) launchCfg . disassemblerArgs . numberOfLines = 100 ; if ( launchCfg . disassemblerArgs . numberOfLines < 1 ) launchCfg . disassemblerArgs . numberOfLines = 1 ; if ( ! launchCfg . disassemblerArgs . hasOwnProperty ( "esxdosRst" ) ) launchCfg . disassemblerArgs . esxdosRst = false ; if ( launchCfg . startAutomatically == undefined ) launchCfg . startAutomatically = false ; if ( launchCfg . commandsAfterLaunch == undefined ) launchCfg . commandsAfterLaunch = [ ] ; if ( launchCfg . zrcp . skipInterrupt == undefined ) launchCfg . zrcp . skipInterrupt = false ; if ( launchCfg . history == undefined ) launchCfg . history = { } as any ; if ( launchCfg . history . reverseDebugInstructionCount == undefined ) launchCfg . history . reverseDebugInstructionCount = 10000 ; if ( launchCfg . history . spotCount == undefined ) launchCfg . history . spotCount = 10 ; if ( launchCfg . history . spotCount > Settings . MAX_HISTORY_SPOT_COUNT ) launchCfg . history . spotCount = Settings . MAX_HISTORY_SPOT_COUNT ; if ( launchCfg . history . spotCount > launchCfg . history . reverseDebugInstructionCount ) launchCfg . history . spotCount = launchCfg . history . reverseDebugInstructionCount ; if ( launchCfg . history . spotCount < 0 ) launchCfg . history . spotCount = 0 ; if ( launchCfg . history . spotShowRegisters == undefined ) launchCfg . history . spotShowRegisters = true ; if ( launchCfg . history . codeCoverageEnabled == undefined ) { if ( launchCfg . remoteType == 'cspect' ) { launchCfg . history . codeCoverageEnabled = false ; } else { launchCfg . history . codeCoverageEnabled = true ; } } if ( ! launchCfg . formatting ) launchCfg . formatting = { registerVar : < any > undefined , registerHover : < any > undefined , bigValues : < any > undefined , smallValues : < any > undefined , watchByte : < any > undefined , watchWord : < any > undefined , stackVar : < any > undefined , } ; if ( ! launchCfg . formatting . registerVar ) launchCfg . formatting . registerVar = [ "AF" , "AF: ${hex}h, F: ${flags}" , "AF'" , "AF': ${hex}h, F': ${flags}" , "PC" , "${hex}h, ${unsigned}u${, :labelsplus|, }" , "SP" , "${hex}h, ${unsigned}u${, :labelsplus|, }" , "IM" , "${unsigned}u" , ".." , "${hex}h, ${unsigned}u, ${signed}i${, :labelsplus|, }" , "F" , "${flags}" , "R" , "${unsigned}u" , "I" , "${hex}h" , "." , "${hex}h, ${unsigned}u, ${signed}i, '${char}', ${bits}b" ] ; if ( ! launchCfg . formatting . registerHover ) launchCfg . formatting . registerHover = [ "AF" , "AF: ${hex}h, F: ${flags}" , "AF'" , "AF': ${hex}h, F': ${flags}" , "PC" , "PC: ${hex}h${\n:labelsplus|\n}" , "SP" , "SP: ${hex}h${\n:labelsplus|\n}" , "IM" , "IM: ${unsigned}u" , ".." , "${name}: ${hex}h, ${unsigned}u, ${signed}i${\n:labelsplus|\n}\n(${hex}h)b=${b@:hex}h, (${hex}h)w=${w@:hex}h" , "R" , "R: ${unsigned}u" , "I" , "I: ${hex}h" , "." , "${name}: ${hex}h, ${unsigned}u, ${signed}i, '${char}', ${bits}b" ] ; if ( ! launchCfg . formatting . bigValues ) launchCfg . formatting . bigValues = "(${hex}h)=${b@:unsigned}/${b@:hex}h/'${b@:char}' or ${w@:hex}h/${w@:unsigned}" ; if ( ! launchCfg . formatting . smallValues ) launchCfg . formatting . smallValues = "${hex}h, ${unsigned}u, ${signed}i, '${char}', ${bits}" ; if ( ! launchCfg . formatting . watchByte ) launchCfg . formatting . watchByte = "${hex}h,\t${unsigned}u,\t${signed}i,\t'${char}',\t${bits}b" ; if ( ! launchCfg . formatting . watchWord ) launchCfg . formatting . watchWord = "${hex}h,\t${unsigned}u,\t${signed}i" ; if ( ! launchCfg . formatting . stackVar ) launchCfg . formatting . stackVar = "${hex}h\t${unsigned}u\t${signed}i\t${{:labels|, |}}" ; if ( ! launchCfg . tabSize ) launchCfg . tabSize = 6 ; if ( ! launchCfg . memoryViewer ) { launchCfg . memoryViewer = { } as any ; } if ( ! launchCfg . memoryViewer . addressColor ) launchCfg . memoryViewer . addressColor = "CornflowerBlue" ; if ( ! launchCfg . memoryViewer . bytesColor ) launchCfg . memoryViewer . bytesColor = "var(--vscode-editor-foreground)" ; if ( ! launchCfg . memoryViewer . asciiColor ) launchCfg . memoryViewer . asciiColor = "OliveDrab" ; if ( ! launchCfg . memoryViewer . addressHoverFormat ) launchCfg . memoryViewer . addressHoverFormat = "${hex}h${\n:labelsplus|\n}" ; if ( ! launchCfg . memoryViewer . valueHoverFormat ) launchCfg . memoryViewer . valueHoverFormat = "${hex}h, ${unsigned}u, ${signed}i, '${char}', ${bits}" ; if ( ! launchCfg . memoryViewer . registerPointerColors ) launchCfg . memoryViewer . registerPointerColors = [ "HL" , "darkgreen" , "DE" , "darkcyan" , "BC" , "dimgray" , "SP" , "goldenrod" , "IX" , "darkorange" , "IY" , "darkviolet" ] ; if ( ! launchCfg . memoryViewer . registersMemoryView ) launchCfg . memoryViewer . registersMemoryView = [ "HL" , "DE" , "BC" , "SP" , "IX" , "IY" ] ; if ( ! launchCfg . unitTestTimeout ) launchCfg . unitTestTimeout = 1 ; return launchCfg ; } param configuration returns public static GetAllAssemblerListFiles ( configuration ) { const listFiles = new Array < AsmConfigBase > ( ) ; if ( configuration . sjasmplus ) listFiles . push ( ... configuration . sjasmplus ) ; if ( configuration . z80asm ) listFiles . push ( ... configuration . z80asm ) ; if ( configuration . z88dk ) listFiles . push ( ... configuration . z88dk ) ; return listFiles ; } /**
     * Checks the settings and throws an exception if something is wrong.
     * E.g. it checks for the existence of file paths.
     * Note: file paths are already expanded to absolute paths.
     */ public static CheckSettings ( ) { const rootFolder = Settings . launch . rootFolder ; if ( ! rootFolder ) throw Error ( "'rootFolder' is empty" ) ; if ( ! fs . existsSync ( rootFolder ) ) throw Error ( "'rootFolder' path(" + rootFolder + ") does not exist." ) ; const rType = Settings . launch . remoteType ; const allowedTypes = [ 'zrcp' , 'cspect' , 'zxnext' , 'zsim' ] ; const found = ( allowedTypes . indexOf ( rType ) >= 0 ) ; if ( ! found ) { throw Error ( "'remoteType': Remote type '" + rType + "' does not exist. Allowed are " + allowedTypes . join ( ', ' ) + "." ) ; } const listFiles = this . GetAllAssemblerListFiles ( Settings . launch ) ; for ( let listFile of listFiles ) { const path = listFile . path ; if ( path == undefined ) throw Error ( "'path': You need to define a path to your file." ) ; if ( ! fs . existsSync ( path ) ) throw Error ( "'path': File '" + path + "' does not exist." ) ; } const customMemory = Settings . launch . zsim . customMemory ; if ( customMemory ) { const nob = customMemory . numberOfBanks ; if ( nob == undefined ) throw Error ( "In 'customMemory' you need to define 'numberOfBanks'." ) ; if ( ( nob <= 0 ) || ( nob & ( nob - 1 ) ) ) throw Error ( "'numberOfBanks' needs to be bigger than 0 and a power of 2." ) ; const banks = customMemory . banks ; if ( banks == undefined ) throw Error ( "In 'customMemory' you need to define 'banks'." ) ; for ( const bank in banks ) { const name = banks [ bank ] ; const bankNr = Utility . parseValue ( bank ) ; if ( isNaN ( bankNr ) ) throw Error ( "Cannot parse '" + bank + "' in 'customMemory'" ) ; if ( ! Number . isInteger ( bankNr ) || bankNr < 0 || bankNr >= nob ) throw Error ( "The bank number in 'customMemory' has to be a non-negative integer which is smaller than the numberOfBanks, but it is set to '" + bank + "'." ) ; const bankType = ( BankType as any ) [ name ] ; if ( bankType == undefined ) { const arr = Utility . getEnumKeys ( BankType ) ; const joined = hjoin . or . q . join ( arr ) ; throw Error ( "Don't understand '" + name + "' in 'customMemory.banks'. Should be " + joined + "." ) ; } } } if ( Settings . launch . zsim . memoryModel == 'CUSTOM' ) { if ( customMemory == undefined ) throw Error ( "If 'memoryModel' is set to 'CUSTOM', you need to define 'customMemory'." ) ; } if ( Settings . launch . z88dk ) { const listFiles = Settings . launch . z88dk ; for ( const listFile of listFiles ) { const mapFile = listFile . mapFile ; if ( mapFile == undefined ) throw Error ( "'z88dk.mapFile': For z88dk you have to define a map file." ) ; if ( ! fs . existsSync ( mapFile ) ) throw Error ( "'z88dk.mapFile': '" + mapFile + "' does not exist." ) ; } } if ( Settings . launch . load ) { if ( ! fs . existsSync ( Settings . launch . load ) ) throw Error ( "'load': File '" + Settings . launch . load + "' does not exist." ) ; if ( Settings . launch . execAddress ) throw Error ( "'execAddress': You load a .sna or .tap file. In that case the execution address is already known from the file and you cannot set it explicitly via 'execAddress'." ) ; } for ( let loadObj of Settings . launch . loadObjs ) { const path = loadObj . path ; if ( ! fs . existsSync ( path ) ) throw Error ( "'loadObj.path': File '" + path + "' does not exist." ) ; if ( loadObj . start == undefined ) throw Error ( "'loadObj.start': You must specify a 'start' address for '" + path + "'." ) ; } } }